Merge commit '2618c50073092e99fe1df5213ba6a0619e908988'

# Conflicts:
#	AsyncDisplayKit.xcodeproj/project.pbxproj
#	Source/Private/ASDisplayNode+AsyncDisplay.mm
This commit is contained in:
Peter Iakovlev 2018-03-02 15:24:20 +04:00
commit 3a2c2ea690
173 changed files with 2517 additions and 1195 deletions

View File

@ -99,6 +99,7 @@
3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */; settings = {ATTRIBUTES = (Private, ); }; };
3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */; };
3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */; };
4496D0731FA9EA6B001CC8D5 /* ASTraitCollectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */; };
4E9127691F64157600499623 /* ASRunLoopQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */; };
509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; };
509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; };
@ -402,11 +403,16 @@
CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */; };
CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */; };
CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */; };
CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */ = {isa = PBXBuildFile; fileRef = CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */; settings = {ATTRIBUTES = (Public, ); }; };
CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */; };
CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */; };
CCE4F9B31F0D60AC00062E4E /* ASIntegerMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */; };
CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */; };
CCE4F9BA1F0DBB5000062E4E /* ASLayoutTestNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */; };
CCE4F9BE1F0ECE5200062E4E /* ASTLayoutFixture.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */; };
CCED5E3E2020D36800395C40 /* ASNetworkImageLoadInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
CCED5E3F2020D36800395C40 /* ASNetworkImageLoadInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */; };
CCED5E412020D49D00395C40 /* ASNetworkImageLoadInfo+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; };
DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; };
DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; };
@ -632,6 +638,7 @@
3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionReusableView.h; sourceTree = "<group>"; };
3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASCollectionReusableView.m; sourceTree = "<group>"; };
3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTableViewTests.mm; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASTraitCollectionTests.m; sourceTree = "<group>"; };
464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = "<group>"; };
4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = "<group>"; };
4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableLayoutController.h; sourceTree = "<group>"; };
@ -895,6 +902,8 @@
CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSParagraphStyle+ASText.m"; sourceTree = "<group>"; };
CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+ASText.h"; sourceTree = "<group>"; };
CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+ASText.m"; sourceTree = "<group>"; };
CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASGraphicsContext.h; sourceTree = "<group>"; };
CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASGraphicsContext.m; sourceTree = "<group>"; };
CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionModernDataSourceTests.m; sourceTree = "<group>"; };
CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSectionController.h; sourceTree = "<group>"; };
CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IGListAdapter+AsyncDisplayKit.h"; sourceTree = "<group>"; };
@ -907,6 +916,9 @@
CCE4F9BB1F0EA67F00062E4E /* debugbreak.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = debugbreak.h; sourceTree = "<group>"; };
CCE4F9BC1F0ECE5200062E4E /* ASTLayoutFixture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTLayoutFixture.h; sourceTree = "<group>"; };
CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTLayoutFixture.mm; sourceTree = "<group>"; };
CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASNetworkImageLoadInfo.h; sourceTree = "<group>"; };
CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASNetworkImageLoadInfo.m; sourceTree = "<group>"; };
CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASNetworkImageLoadInfo+Private.h"; sourceTree = "<group>"; };
D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = "<group>"; };
D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; };
D785F6611A74327E00291744 /* ASScrollNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASScrollNode.mm; sourceTree = "<group>"; };
@ -1068,6 +1080,8 @@
058D09B1195D04C000B7D73C /* Source */ = {
isa = PBXGroup;
children = (
CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */,
CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */,
CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */,
CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */,
DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */,
@ -1252,6 +1266,7 @@
695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */,
699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */,
4E9127681F64157600499623 /* ASRunLoopQueueTests.m */,
4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */,
);
path = Tests;
sourceTree = "<group>";
@ -1270,6 +1285,8 @@
058D09E1195D050800B7D73C /* Details */ = {
isa = PBXGroup;
children = (
CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */,
CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */,
CC5601391F06E9A700DC4FBE /* ASIntegerMap.h */,
CC56013A1F06E9A700DC4FBE /* ASIntegerMap.mm */,
CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */,
@ -1367,6 +1384,7 @@
058D0A01195D050800B7D73C /* Private */ = {
isa = PBXGroup;
children = (
CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */,
CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */,
CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */,
CCA282C61E9EB64B0037E8B7 /* ASDisplayNodeTipState.h */,
@ -1836,6 +1854,7 @@
68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */,
CCCCCCE11EC3EF060087FE10 /* ASTextUtilities.h in Headers */,
B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */,
CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */,
E5C347B11ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h in Headers */,
CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */,
B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */,
@ -1903,6 +1922,7 @@
DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */,
CC57EAF81E3939450034C595 /* ASTableView+Undeprecated.h in Headers */,
254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */,
CCED5E412020D49D00395C40 /* ASNetworkImageLoadInfo+Private.h in Headers */,
9CDC18CD1B910E12004965E2 /* ASLayoutElementPrivate.h in Headers */,
B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */,
B35062211B010EFD0018CF92 /* ASLayoutRangeType.h in Headers */,
@ -1954,6 +1974,7 @@
B35062391B010EFD0018CF92 /* ASThread.h in Headers */,
2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */,
509E68651B3AEDC5009B9150 /* CoreGraphics+ASConvenience.h in Headers */,
CCED5E3E2020D36800395C40 /* ASNetworkImageLoadInfo.h in Headers */,
B350623A1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.h in Headers */,
044284FF1BAA3BD600D16268 /* UICollectionViewLayout+ASConvenience.h in Headers */,
B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */,
@ -2159,6 +2180,7 @@
buildActionMask = 2147483647;
files = (
E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.m in Sources */,
4496D0731FA9EA6B001CC8D5 /* ASTraitCollectionTests.m in Sources */,
29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */,
CC583AD71EF9BDC100134156 /* NSInvocation+ASTestHelpers.m in Sources */,
CC051F1F1D7A286A006434CB /* ASCALayerTests.m in Sources */,
@ -2271,6 +2293,7 @@
E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */,
9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */,
690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */,
CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.m in Sources */,
CCCCCCD81EC3EF060087FE10 /* ASTextInput.m in Sources */,
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */,
@ -2283,6 +2306,7 @@
E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */,
68FC85EC1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */,
CC55A7121E52A0F200594372 /* ASResponderChainEnumerator.m in Sources */,
CCED5E3F2020D36800395C40 /* ASNetworkImageLoadInfo.m in Sources */,
68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */,
E5775B041F16759F00CAC9BC /* ASCollectionLayoutCache.mm in Sources */,
9C70F20A1CDBE949007D6C76 /* ASTableNode.mm in Sources */,

View File

@ -1,9 +1,36 @@
## master
* Add your own contributions to the next release on the line below this with your name.
- [ASRunloopQueue] Introduce new runloop queue(ASCATransactionQueue) to coalesce Interface state update calls for view controller transitions.
- [ASRangeController] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling.
- **Important** ASDisplayNode's cornerRadius is a new thread-safe bridged property that should be preferred over CALayer's. Use the latter at your own risk! [Huy Nguyen](https://github.com/nguyenhuy) [#749](https://github.com/TextureGroup/Texture/pull/749).
- [ASCellNode] Adds mapping for UITableViewCell focusStyle [Alex Hill](https://github.com/alexhillc) [#727](https://github.com/TextureGroup/Texture/pull/727)
- [ASNetworkImageNode] Fix capturing self in the block while loading image in ASNetworkImageNode. [Denis Mororozov](https://github.com/morozkin) [#777](https://github.com/TextureGroup/Texture/pull/777)
- [ASTraitCollection] Add new properties of UITraitCollection to ASTraitCollection. [Yevgen Pogribnyi](https://github.com/ypogribnyi)
- [ASRectMap] Replace implementation of ASRectTable with a simpler one based on unordered_map.[Scott Goodson](https://github.com/appleguy) [#719](https://github.com/TextureGroup/Texture/pull/719)
- [ASCollectionView] Add missing flags for ASCollectionDelegate [Ilya Zheleznikov](https://github.com/ilyailya) [#718](https://github.com/TextureGroup/Texture/pull/718)
- [ASNetworkImageNode] Deprecates .URLs in favor of .URL [Garrett Moon](https://github.com/garrettmoon) [#699](https://github.com/TextureGroup/Texture/pull/699)
- [iOS11] Update project settings and fix errors [Eke](https://github.com/Eke) [#676](https://github.com/TextureGroup/Texture/pull/676)
- [ASCornerLayoutSpec] New layout spec class for declarative corner element layout. [#657](https://github.com/TextureGroup/Texture/pull/657) [huangkun](https://github.com/huang-kun)
- [ASDisplayNode layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695)
- [ASDisplayNode layout] Fix an issue that sometimes causes a node's pending layout to not be applied. [Huy Nguyen](https://github.com/nguyenhuy) [#792](https://github.com/TextureGroup/Texture/pull/792)
- [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy)
- [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy)
- Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424)
- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so subnodes can start preloading right away. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706)
- [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler)
- Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler)
- Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler)
- **Breaking** Changes to ASNetworkImageNode: [Adlai Holler](https://github.com/Adlai-Holler)
- Modified `ASImageDownloaderCompletion` to add an optional `id userInfo` field. Your custom downloader can pass `nil`.
- Modified the last argument to `-[ASNetworkImageNodeDelegate imageNode:didLoadImage:info:]` method from a struct to an object of new class `ASNetworkImageLoadInfo` which includes other metadata about the load operation.
- Removed +load static initializer from ASDisplayNode. [Adlai Holler](https://github.com/Adlai-Holler)
- Optimized ASNetworkImageNode loading and resolved edge cases where the image provided to the delegate was not the image that was loaded. [Adlai Holler](https://github.com/Adlai-Holler) [#778](https://github.com/TextureGroup/Texture/pull/778/)
- Make `ASCellNode` tint color apply to table view cell accessories. [Vladyslav Chapaev](https://github.com/ShogunPhyched) [#764](https://github.com/TextureGroup/Texture/pull/764)
- Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening. [Michael Schneider](https://github.com/maicki) [#794](https://github.com/TextureGroup/Texture/pull/778/)
- Pass scrollViewWillEndDragging delegation through in ASIGListAdapterDataSource for IGListKit integration. [#796](https://github.com/TextureGroup/Texture/pull/796)
## 2.6
- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon)
- [ASCollectionView] Improve performance and behavior of rotation / bounds changes. [Scott Goodson](https://github.com/appleguy) [#431](https://github.com/TextureGroup/Texture/pull/431)
- [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy)
- [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon)
@ -13,15 +40,6 @@
- Updated to be backwards compatible with Xcode 8. [Adlai Holler](https://github.com/Adlai-Holler)
- [API CHANGES] `ASPerformMainThreadDeallocation` and `ASPerformBackgroundDeallocation` functions take `id *` instead of `id` and they're now more reliable. Also, in Swift, `ASDeallocQueue.sharedDeallocationQueue() -> ASDeallocQueue.sharedDeallocationQueue`. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/651)
- [Collection/Table] Added direct support for mapping section indexes between data spaces. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/660)
- [ASCornerLayoutSpec] New layout spec class for declarative corner element layout. [#657](https://github.com/TextureGroup/Texture/pull/657) [huangkun](https://github.com/huang-kun)
- [Layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695)
- [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy)
- [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy)
- Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424)
- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706)
## 2.6
- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon)
## 2.5.1
- [ASVideoNode] Fix unreleased time observer. [Flo Vouin](https://github.com/flovouin)

View File

@ -1,2 +1,2 @@
github "pinterest/PINRemoteImage" "3.0.0-beta.13"
github "pinterest/PINCache"
github "pinterest/PINCache" "3.0.1-beta.6"

View File

@ -52,7 +52,11 @@ def check_file_header(files_to_check, licenses)
correct_license = false
licenses.each do |license|
license_header = full_license(license, filename)
if data.start_with?(license_header)
# Hack for https://github.com/TextureGroup/Texture/issues/745
# If it's already a "modified-post-Texture" file, leave it with it original copyright year.
if data.include? "Modifications to this file made after 4/13/2017"
correct_license = true
elsif data.start_with?(license_header)
correct_license = true
end
end
@ -67,7 +71,7 @@ end
# Ensure new files have proper header
new_source_license_header = <<-HEREDOC
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@ -87,7 +91,7 @@ modified_source_license_header = <<-HEREDOC
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

View File

@ -1,6 +1,6 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
platform :ios, '9.0'
target :'AsyncDisplayKitTests' do
pod 'OCMock', '~> 3.4'

View File

@ -192,6 +192,12 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
*/
@property (nonatomic) UITableViewCellSelectionStyle selectionStyle;
/* @abstract The focus style when a cell is focused
* @default UITableViewCellFocusStyleDefault
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
*/
@property (nonatomic) UITableViewCellFocusStyle focusStyle;
/* @abstract The view used as the background of the cell when it is selected.
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
* ASCollectionView uses these properties when configuring UICollectionViewCells that host ASCellNodes.

View File

@ -60,6 +60,7 @@
// Use UITableViewCell defaults
_selectionStyle = UITableViewCellSelectionStyleDefault;
_focusStyle = UITableViewCellFocusStyleDefault;
self.clipsToBounds = YES;
return self;
@ -89,7 +90,7 @@
if ([_viewController isKindOfClass:[ASViewController class]]) {
ASViewController *asViewController = (ASViewController *)_viewController;
_viewControllerNode = asViewController.node;
[_viewController view];
[_viewController loadViewIfNeeded];
} else {
// Careful to avoid retain cycle
UIViewController *viewController = _viewController;

View File

@ -632,6 +632,32 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (NSArray<NSString *> *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section;
/**
* Asks the data source if it's possible to move the specified item interactively.
*
* See @p -[UICollectionViewDataSource collectionView:canMoveItemAtIndexPath:] @c.
*
* @param collectionNode The sender.
* @param node The display node for the item that may be moved.
*
* @return Whether the item represented by @p node may be moved.
*/
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canMoveItemWithNode:(ASCellNode *)node;
/**
* Called when the user has interactively moved an item. The data source
* should update its internal data store to reflect the move. Note that you
* should not call [collectionNode moveItemAtIndexPath:toIndexPath:] the
* collection node's internal state will be updated automatically.
*
* * See @p -[UICollectionViewDataSource collectionView:moveItemAtIndexPath:toIndexPath:] @c.
*
* @param collectionNode The sender.
* @param sourceIndexPath The original item index path.
* @param destinationIndexPath The new item index path.
*/
- (void)collectionNode:(ASCollectionNode *)collectionNode moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
/**
* Similar to -collectionView:cellForItemAtIndexPath:.
*

View File

@ -173,6 +173,18 @@
return self;
}
#if ASDISPLAYNODE_ASSERTIONS_ENABLED
- (void)dealloc
{
if (self.nodeLoaded) {
__weak UIView *view = self.view;
ASPerformBlockOnMainThread(^{
ASDisplayNodeCAssertNil(view.superview, @"Node's view should be removed from hierarchy.");
});
}
}
#endif
#pragma mark ASDisplayNode
- (void)didLoad

View File

@ -101,6 +101,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
NSHashTable<ASCellNode *> *_cellsForLayoutUpdates;
id<ASCollectionViewLayoutFacilitatorProtocol> _layoutFacilitator;
CGFloat _leadingScreensForBatching;
// When we update our data controller in response to an interactive move,
// we don't want to tell the collection view about the change (it knows!)
BOOL _updatingInResponseToInteractiveMove;
BOOL _inverted;
NSUInteger _superBatchUpdateCount;
@ -121,15 +125,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
ASCollectionViewInvalidationStyle _nextLayoutInvalidationStyle;
/**
* Our layer, retained. Under iOS < 9, when collection views are removed from the hierarchy,
* their layers may be deallocated and become dangling pointers. This puts the collection view
* into a very dangerous state where pretty much any call will crash it. So we manually retain our layer.
*
* You should never access this, and it will be nil under iOS >= 9.
*/
CALayer *_retainedLayer;
/**
* If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it.
@ -156,7 +151,12 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
* Counter used to keep track of nested batch updates.
*/
NSInteger _batchUpdateCount;
/**
* Keep a strong reference to node till view is ready to release.
*/
ASCollectionNode *_keepalive_node;
struct {
unsigned int scrollViewDidScroll:1;
unsigned int scrollViewWillBeginDragging:1;
@ -218,6 +218,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
unsigned int numberOfSectionsInCollectionNode:1;
unsigned int collectionNodeNumberOfItemsInSection:1;
unsigned int collectionNodeContextForSection:1;
unsigned int collectionNodeCanMoveItem:1;
unsigned int collectionNodeMoveItem:1;
// Whether this data source conforms to ASCollectionDataSourceInterop
unsigned int interop:1;
@ -310,10 +312,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
[self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:kReuseIdentifier];
if (!AS_AT_LEAST_IOS9) {
_retainedLayer = self.layer;
}
[self _configureCollectionViewLayout:layout];
return self;
@ -454,6 +452,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
_asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)];
_asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)];
_asyncDataSourceFlags.nodeModelForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeModelForItemAtIndexPath:)];
_asyncDataSourceFlags.collectionNodeCanMoveItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:canMoveItemWithNode:)];
_asyncDataSourceFlags.collectionNodeMoveItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:moveItemAtIndexPath:toIndexPath:)];
_asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)];
if (_asyncDataSourceFlags.interop) {
@ -1492,15 +1492,72 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath
{
// If a scroll happenes the current range mode needs to go to full
ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController];
if (ASInterfaceStateIncludesVisible(interfaceState)) {
[_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull];
[self _checkForBatchFetching];
// Mimic UIKit's gating logic.
// If the data source doesn't support moving, then all bets are off.
if (!_asyncDataSourceFlags.collectionNodeMoveItem) {
return NO;
}
// Currently we do not support interactive moves when using async layout. The reason is, we do not have a mechanism
// to propagate the "presentation data" element map (containing the speculative in-progress moves) to the layout delegate,
// and this can cause exceptions to be thrown from UICV. For example, if you drag an item out of a section,
// the element map will still contain N items in that section, even though there's only N-1 shown, and UICV will
// throw an exception that you specified an element that doesn't exist.
//
// In iOS >= 11, this is made much easier by the UIDataSourceTranslating API. In previous versions of iOS our best bet
// would be to capture the invalidation contexts that are sent during interactive moves and make our own data source translator.
if ([self.collectionViewLayout isKindOfClass:[ASCollectionLayout class]]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
as_log_debug(ASCollectionLog(), "Collection node item interactive movement is not supported when using a layout delegate. This message will only be logged once. Node: %@", ASObjectDescriptionMakeTiny(self));
});
return NO;
}
// If the data source implements canMoveItem, let them decide.
if (_asyncDataSourceFlags.collectionNodeCanMoveItem) {
if (auto cellNode = [self nodeForItemAtIndexPath:indexPath]) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
return [_asyncDataSource collectionNode:collectionNode canMoveItemWithNode:cellNode];
}
}
// Otherwise allow the move for all items.
return YES;
}
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeMoveItem, @"Should not allow interactive collection item movement if data source does not support it.");
// Inform the data source first, in case they call nodeForItemAtIndexPath:.
// We want to make sure we return them the node for the item they have in mind.
if (auto collectionNode = self.collectionNode) {
[_asyncDataSource collectionNode:collectionNode moveItemAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];
}
// Now we update our data controller's store.
// Get up to date
[self waitUntilAllUpdatesAreCommitted];
// Set our flag to suppress informing super about the change.
ASDisplayNodeAssertFalse(_updatingInResponseToInteractiveMove);
_updatingInResponseToInteractiveMove = YES;
// Submit the move
[self moveItemAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];
// Wait for it to finish should be fast!
[self waitUntilAllUpdatesAreCommitted];
// Clear the flag
_updatingInResponseToInteractiveMove = NO;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController];
if (ASInterfaceStateIncludesVisible(interfaceState)) {
[self _checkForBatchFetching];
}
for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) {
// _cellsForVisibilityUpdates only includes cells for ASCellNode subclasses with overrides of the visibility method.
[cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged inScrollView:scrollView];
@ -1539,6 +1596,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
// If a scroll happens the current range mode needs to go to full
_rangeController.contentHasBeenScrolled = YES;
[_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull];
for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) {
[cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging inScrollView:scrollView];
}
@ -1971,28 +2032,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
return _rangeController;
}
/// The UIKit version of this method is only available on iOS >= 9
- (NSArray<NSIndexPath *> *)asdk_indexPathsForVisibleSupplementaryElementsOfKind:(NSString *)kind
{
if (AS_AVAILABLE_IOS(9)) {
return [self indexPathsForVisibleSupplementaryElementsOfKind:kind];
}
// iOS 8 workaround
// We cannot use willDisplaySupplementaryView/didEndDisplayingSupplementaryView
// because those methods send index paths for _deleted items_ (invalid index paths)
[self layoutIfNeeded];
NSArray<UICollectionViewLayoutAttributes *> *visibleAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:self.bounds];
NSMutableArray *result = [NSMutableArray array];
for (UICollectionViewLayoutAttributes *attributes in visibleAttributes) {
if (attributes.representedElementCategory == UICollectionElementCategorySupplementaryView
&& [attributes.representedElementKind isEqualToString:kind]) {
[result addObject:attributes.indexPath];
}
}
return result;
}
- (NSHashTable<ASCollectionElement *> *)visibleElementsForRangeController:(ASRangeController *)rangeController
{
return ASPointerTableByFlatMapping(_visibleElements, id element, element);
@ -2023,7 +2062,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
- (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
if (!self.asyncDataSource || _superIsPendingDataLoad || _updatingInResponseToInteractiveMove) {
updates();
[changeSet executeCompletionHandlerWithFinished:NO];
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
@ -2216,6 +2255,20 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
}
}
- (void)willMoveToSuperview:(UIView *)newSuperview
{
if (self.superview == nil && newSuperview != nil) {
_keepalive_node = self.collectionNode;
}
}
- (void)didMoveToSuperview
{
if (self.superview == nil) {
_keepalive_node = nil;
}
}
#pragma mark ASCALayerExtendedDelegate
/**
@ -2278,23 +2331,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
return;
}
#if ASDISPLAYNODE_ASSERTIONS_ENABLED // Remove implementations entirely for efficiency if not asserting.
// intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage)
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0)
{
ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd));
return NO;
}
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0)
{
ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd));
}
#endif
@end
#endif

View File

@ -2,8 +2,13 @@
// ASDisplayNode+Layout.mm
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
@ -304,13 +309,24 @@ ASLayoutElementStyleExtensibilityForwarding
}
CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size);
NSUInteger calculatedVersion = _calculatedDisplayNodeLayout->version;
// Prefer a newer and not yet applied _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout
// If there is no such _pending, check if _calculated is valid to reuse (avoiding recalculation below).
BOOL pendingLayoutIsPreferred = (_pendingDisplayNodeLayout != nullptr
&& _pendingDisplayNodeLayout->version >= _layoutVersion
&& _pendingDisplayNodeLayout->version > _calculatedDisplayNodeLayout->version); // _pending is not yet applied
BOOL calculatedLayoutIsReusable = (_calculatedDisplayNodeLayout->version >= _layoutVersion
BOOL pendingLayoutIsPreferred = NO;
if (_pendingDisplayNodeLayout != nullptr) {
NSUInteger pendingVersion = _pendingDisplayNodeLayout->version;
if (pendingVersion >= _layoutVersion) {
if (pendingVersion > calculatedVersion) {
pendingLayoutIsPreferred = YES; // Newer _pending
} else if (pendingVersion == calculatedVersion
&& !ASSizeRangeEqualToSizeRange(_pendingDisplayNodeLayout->constrainedSize,
_calculatedDisplayNodeLayout->constrainedSize)) {
pendingLayoutIsPreferred = YES; // _pending with a different constrained size
}
}
}
BOOL calculatedLayoutIsReusable = (calculatedVersion >= _layoutVersion
&& (_calculatedDisplayNodeLayout->requestedLayoutFromAbove
|| CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout)));
if (!pendingLayoutIsPreferred && calculatedLayoutIsReusable) {

View File

@ -94,12 +94,10 @@
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute
{
if (AS_AT_LEAST_IOS9) {
UIUserInterfaceLayoutDirection layoutDirection =
[UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute];
self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight
? YGDirectionLTR : YGDirectionRTL);
}
UIUserInterfaceLayoutDirection layoutDirection =
[UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute];
self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight
? YGDirectionLTR : YGDirectionRTL);
}
- (void)setYogaParent:(ASDisplayNode *)yogaParent

View File

@ -666,6 +666,12 @@ extern NSInteger const ASDefaultDrawingPriority;
* @default ASCornerRoundingTypeDefaultSlowCALayer
*/
@property (nonatomic, assign) ASCornerRoundingType cornerRoundingType; // default=Slow CALayer .cornerRadius (offscreen rendering)
/** @abstract The radius to use when rounding corners of the ASDisplayNode.
*
* @discussion This property is thread-safe and should always be preferred over CALayer's cornerRadius property,
* even if corner rounding type is ASCornerRoundingTypeDefaultSlowCALayer.
*/
@property (nonatomic, assign) CGFloat cornerRadius; // default=0.0
@property (nonatomic, assign) BOOL clipsToBounds; // default==NO

View File

@ -36,6 +36,7 @@
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
#import <AsyncDisplayKit/ASLayoutSpec.h>
@ -62,7 +63,7 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority;
// We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10
@protocol CALayerDelegate;
@interface ASDisplayNode () <UIGestureRecognizerDelegate, CALayerDelegate, _ASDisplayLayerDelegate>
@interface ASDisplayNode () <UIGestureRecognizerDelegate, CALayerDelegate, _ASDisplayLayerDelegate, ASCATransactionQueueObserving>
/**
* See ASDisplayNodeInternal.h for ivars
@ -225,12 +226,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@");
}
+ (void)load
{
// Ensure this value is cached on the main thread before needed in the background.
ASScreenScale();
}
+ (Class)viewClass
{
return [_ASDisplayView class];
@ -258,6 +253,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
_viewClass = [self.class viewClass];
_layerClass = [self.class layerClass];
BOOL isSynchronous = ![_viewClass isSubclassOfClass:[_ASDisplayView class]]
|| ![_layerClass isSubclassOfClass:[_ASDisplayLayer class]];
setFlag(Synchronous, isSynchronous);
_contentsScaleForDisplay = ASScreenScale();
_drawingPriority = ASDefaultDrawingPriority;
@ -1508,7 +1508,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
BOOL isRight = (idx == 1 || idx == 2);
CGSize size = CGSizeMake(radius + 1, radius + 1);
UIGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay);
ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay);
CGContextRef ctx = UIGraphicsGetCurrentContext();
if (isRight == YES) {
@ -1525,11 +1525,9 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
// No lock needed, as _clipCornerLayers is only modified on the main thread.
CALayer *clipCornerLayer = _clipCornerLayers[idx];
clipCornerLayer.contents = (id)(UIGraphicsGetImageFromCurrentImageContext().CGImage);
clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage);
clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height);
clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0);
UIGraphicsEndImageContext();
}
[self _layoutClipCornersIfNeeded];
});
@ -2742,7 +2740,7 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
// Entered or exited range managed state.
if ((newState & ASHierarchyStateRangeManaged) != (oldState & ASHierarchyStateRangeManaged)) {
if (newState & ASHierarchyStateRangeManaged) {
[self enterInterfaceState:self.supernode.interfaceState];
[self enterInterfaceState:self.supernode.pendingInterfaceState];
} else {
// The case of exiting a range-managed state should be fairly rare. Adding or removing the node
// to a view hierarchy will cause its interfaceState to be either fully set or unset (all fields),
@ -2785,30 +2783,34 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode");
ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive");
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
if (![self supportsRangeManagedInterfaceState]) {
self.interfaceState = ASInterfaceStateNone;
} else {
// This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange:NO callback, as part our API guarantee that this method can be used for
// things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail.
// Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the
// same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed).
// TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer
// integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call.
if (ASInterfaceStateIncludesVisible(self.interfaceState)) {
dispatch_async(dispatch_get_main_queue(), ^{
// This block intentionally retains self.
__instanceLock__.lock();
unsigned isInHierarchy = _flags.isInHierarchy;
BOOL isVisible = ASInterfaceStateIncludesVisible(_interfaceState);
ASInterfaceState newState = (_interfaceState & ~ASInterfaceStateVisible);
__instanceLock__.unlock();
if (!isInHierarchy && isVisible) {
self.interfaceState = newState;
// This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange:NO callback, as part our API guarantee that this method can be used for
// things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail.
// Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the
// same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed).
// TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer
// integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call.
if (ASInterfaceStateIncludesVisible(_pendingInterfaceState)) {
void(^exitVisibleInterfaceState)(void) = ^{
// This block intentionally retains self.
__instanceLock__.lock();
unsigned isStillInHierarchy = _flags.isInHierarchy;
BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState);
ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible);
__instanceLock__.unlock();
if (!isStillInHierarchy && isVisible) {
if (![self supportsRangeManagedInterfaceState]) {
newState = ASInterfaceStateNone;
}
});
self.interfaceState = newState;
}
};
if ([[ASCATransactionQueue sharedQueue] disabled]) {
dispatch_async(dispatch_get_main_queue(), exitVisibleInterfaceState);
} else {
exitVisibleInterfaceState();
}
}
}
@ -2869,25 +2871,53 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
}
- (void)setInterfaceState:(ASInterfaceState)newState
{
if ([[ASCATransactionQueue sharedQueue] disabled]) {
[self applyPendingInterfaceState:newState];
} else {
ASDN::MutexLocker l(__instanceLock__);
if (_pendingInterfaceState != newState) {
_pendingInterfaceState = newState;
[[ASCATransactionQueue sharedQueue] enqueue:self];
}
}
}
- (ASInterfaceState)pendingInterfaceState
{
ASDN::MutexLocker l(__instanceLock__);
return _pendingInterfaceState;
}
- (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState
{
//This method is currently called on the main thread. The assert has been added here because all of the
//did(Enter|Exit)(Display|Visible|Preload)State methods currently guarantee calling on main.
ASDisplayNodeAssertMainThread();
// It should never be possible for a node to be visible but not be allowed / expected to display.
ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState));
// This method manages __instanceLock__ itself, to ensure the lock is not held while didEnter/Exit(.*)State methods are called, thus avoid potential deadlocks
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
ASInterfaceState oldState = ASInterfaceStateNone;
ASInterfaceState newState = ASInterfaceStateNone;
{
ASDN::MutexLocker l(__instanceLock__);
if (_interfaceState == newState) {
return;
// newPendingState will not be used when ASCATransactionQueue is enabled
// and use _pendingInterfaceState instead for interfaceState update.
if ([[ASCATransactionQueue sharedQueue] disabled]) {
_pendingInterfaceState = newPendingState;
}
oldState = _interfaceState;
newState = _pendingInterfaceState;
if (newState == oldState) {
return;
}
_interfaceState = newState;
}
// It should never be possible for a node to be visible but not be allowed / expected to display.
ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState));
// TODO: Trigger asynchronous measurement if it is not already cached or being calculated.
// if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) {
// }
@ -2984,6 +3014,12 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
[self interfaceStateDidChange:newState fromState:oldState];
}
- (void)prepareForCATransactionCommit
{
// Apply _pendingInterfaceState actual _interfaceState, note that ASInterfaceStateNone is not used.
[self applyPendingInterfaceState:ASInterfaceStateNone];
}
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
{
// Subclass hook
@ -3081,16 +3117,15 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
// If this node has ASM enabled and is not yet visible, force a layout pass to apply its applicable pending layout, if any,
// so that its subnodes are inserted/deleted and start preloading right away.
//
// - If it has an up-to-date layout (and subnodes), calling -layoutIfNeeded will be fast.
//
// - If it doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur
// (see -__layout and -_u_measureNodeWithBoundsIfNecessary:). This scenario is uncommon,
// and running a measurement pass here is a fine trade-off because preloading any time after this point would be late.
if (self.automaticallyManagesSubnodes) {
// Tell the node to apply its applicable pending layout, if any, so that its subnodes are inserted/deleted
// and start preloading right away.
//
// If this node has an up-to-date layout (and subnodes), calling layoutIfNeeded will be fast.
//
// If this node doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur
// (see __layout and _u_measureNodeWithBoundsIfNecessary:).
// This scenario should be uncommon, and running a measurement pass here is a fine trade-off because preloading
// any time after this point would be late.
[self layoutIfNeeded];
}

View File

@ -219,6 +219,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)editableTextNodeShouldPaste:(ASEditableTextNode *)editableTextNode;
- (ASEditableTextNodeTargetForAction * _Nullable)editableTextNodeTargetForAction:(SEL)action;
- (BOOL)editableTextNodeShouldReturn:(ASEditableTextNode *)editableTextNode;
@end

View File

@ -95,6 +95,7 @@
@property (nonatomic, copy) bool (^shouldPaste)();
@property (nonatomic, copy) ASEditableTextNodeTargetForAction *(^targetForActionImpl)(SEL);
@property (nonatomic, copy) bool (^shouldReturn)();
@end
@ -161,6 +162,19 @@
}
}
- (NSArray *)keyCommands {
UIKeyCommand *plainReturn = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:kNilOptions action:@selector(handlePlainReturn:)];
return @[
plainReturn
];
}
- (void)handlePlainReturn:(id)__unused sender {
if (_shouldReturn) {
_shouldReturn();
}
}
#endif
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
@ -305,6 +319,15 @@
}
return nil;
};
textView.shouldReturn = ^bool {
__strong ASEditableTextNode *strongSelf = weakSelf;
if (strongSelf != nil) {
if ([strongSelf->_delegate respondsToSelector:@selector(editableTextNodeShouldReturn:)]) {
return [strongSelf->_delegate editableTextNodeShouldReturn:strongSelf];
}
}
return true;
};
_textKitComponents.textView = textView;
_textKitComponents.textView.scrollEnabled = _scrollEnabled;
_textKitComponents.textView.delegate = self;

View File

@ -25,6 +25,7 @@
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
@ -218,11 +219,10 @@ typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry);
ASDN::MutexLocker l(__instanceLock__);
UIGraphicsBeginImageContext(size);
ASGraphicsBeginImageContextWithOptions(size, NO, 1);
[self.placeholderColor setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
return image;
}
@ -482,7 +482,7 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex;
+ (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
{
// The following `UIGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an
// The following `ASGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an
// A5 processor for a 400x800 backingSize.
// Check for cancellation before we call it.
if (isCancelled()) {
@ -491,7 +491,7 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex;
// Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds
// will do its rounding on pixel instead of point boundaries
UIGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0);
ASGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0);
BOOL contextIsClean = YES;
@ -532,16 +532,13 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex;
key.didDisplayNodeContentWithRenderingContext(context, drawParameters);
}
// The following `UIGraphicsGetImageFromCurrentImageContext` call will commonly take more than 20ms on an
// A5 processor. Check for cancellation before we call it.
// Check cancellation one last time before forming image.
if (isCancelled()) {
UIGraphicsEndImageContext();
ASGraphicsEndImageContext();
return nil;
}
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *result = ASGraphicsGetImageAndEndCurrentContext();
if (key.imageModificationBlock) {
result = key.imageModificationBlock(result);
@ -752,7 +749,7 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex;
extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor)
{
return ^(UIImage *originalImage) {
UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}];
// Make the image round
@ -768,24 +765,21 @@ extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(
[roundOutline stroke];
}
UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return modifiedImage;
return ASGraphicsGetImageAndEndCurrentContext();
};
}
extern asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color)
{
return ^(UIImage *originalImage) {
UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
// Set color and render template
[color setFill];
UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[templateImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];
UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *modifiedImage = ASGraphicsGetImageAndEndCurrentContext();
// if the original image was stretchy, keep it stretchy
if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) {

View File

@ -26,6 +26,7 @@
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASLayout.h>
@ -224,7 +225,7 @@
CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale);
ASGraphicsBeginImageContextWithOptions(image.size, YES, image.scale);
[image drawAtPoint:CGPointZero];
UIImage *pinImage;
@ -256,8 +257,7 @@
}
}
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
image = ASGraphicsGetImageAndEndCurrentContext();
}
strongSelf.image = image;

View File

@ -625,7 +625,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
finishedLoadingBlock(downloadedImage, nextImageIdentifier, error);
}];
}
// Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly.
// Likewise, if it's a Photos asset, we need to fetch it accordingly.
else if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) {
[self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) {
as_log_verbose(ASImageLoadingLog(), "Acquired image from Photos for %@ %@", weakSelf, nextImageIdentifier);
@ -673,7 +673,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required");
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");
// ALAssetsLibrary was replaced in iOS 8 and deprecated in iOS 9.
// We'll drop support very soon.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init];
[assetLibrary assetForURL:assetURL resultBlock:^(ALAsset *asset) {
@ -685,6 +689,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
} failureBlock:^(NSError *error) {
completionBlock(nil, error);
}];
#pragma clang diagnostic pop
}
- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock
@ -818,7 +823,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
[self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL
callbackQueue:dispatch_get_main_queue()
downloadProgress:downloadProgressBlock
completion:^(id <ASImageContainerProtocol> imageContainer, NSError *error, id downloadIdentifier) {
completion:^(id <ASImageContainerProtocol> imageContainer, NSError *error, id downloadIdentifier, id userInfo) {
// We dereference iVars directly, so we can't have weakSelf going nil on us.
__typeof__(self) strongSelf = weakSelf;
if (!strongSelf)

View File

@ -0,0 +1,39 @@
//
// ASNetworkImageLoadInfo.h
// AsyncDisplayKit
//
// Created by Adlai on 1/30/18.
// Copyright © 2018 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, ASNetworkImageSourceType) {
ASNetworkImageSourceUnspecified = 0,
ASNetworkImageSourceSynchronousCache,
ASNetworkImageSourceAsynchronousCache,
ASNetworkImageSourceFileURL,
ASNetworkImageSourceDownload,
};
AS_SUBCLASSING_RESTRICTED
@interface ASNetworkImageLoadInfo : NSObject <NSCopying>
/// The type of source from which the image was loaded.
@property (readonly) ASNetworkImageSourceType sourceType;
/// The image URL that was downloaded.
@property (readonly) NSURL *url;
/// The download identifier, if one was provided.
@property (nullable, readonly) id downloadIdentifier;
/// The userInfo object provided by the downloader, if one was provided.
@property (nullable, readonly) id userInfo;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,32 @@
//
// ASNetworkImageLoadInfo.m
// AsyncDisplayKit
//
// Created by Adlai on 1/30/18.
// Copyright © 2018 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/ASNetworkImageLoadInfo.h>
#import <AsyncDisplayKit/ASNetworkImageLoadInfo+Private.h>
@implementation ASNetworkImageLoadInfo
- (instancetype)initWithURL:(NSURL *)url sourceType:(ASNetworkImageSourceType)sourceType downloadIdentifier:(id)downloadIdentifier userInfo:(id)userInfo
{
if (self = [super init]) {
_url = [url copy];
_sourceType = sourceType;
_downloadIdentifier = downloadIdentifier;
_userInfo = userInfo;
}
return self;
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
@end

View File

@ -22,6 +22,7 @@
NS_ASSUME_NONNULL_BEGIN
@protocol ASNetworkImageNodeDelegate, ASImageCacheProtocol, ASImageDownloaderProtocol;
@class ASNetworkImageLoadInfo;
/**
@ -136,20 +137,6 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark -
typedef NS_ENUM(NSInteger, ASNetworkImageSource) {
ASNetworkImageSourceUnspecified = 0,
ASNetworkImageSourceSynchronousCache,
ASNetworkImageSourceAsynchronousCache,
ASNetworkImageSourceFileURL,
ASNetworkImageSourceDownload,
};
/// A struct that carries details about ASNetworkImageNode's image loads.
typedef struct {
/// The source from which the image was loaded.
ASNetworkImageSource imageSource;
} ASNetworkImageNodeDidLoadInfo;
/**
* The methods declared by the ASNetworkImageNodeDelegate protocol allow the adopting delegate to respond to
* notifications such as finished decoding and downloading an image.
@ -163,11 +150,11 @@ typedef struct {
*
* @param imageNode The sender.
* @param image The newly-loaded image.
* @param info Misc information about the image load.
* @param info Additional information about the image load.
*
* @discussion Called on a background queue.
*/
- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageNodeDidLoadInfo)info;
- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageLoadInfo *)info;
/**
* Notification that the image node finished downloading an image.

View File

@ -28,6 +28,7 @@
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
#import <AsyncDisplayKit/ASImageContainerProtocolCategories.h>
#import <AsyncDisplayKit/ASLog.h>
#import <AsyncDisplayKit/ASNetworkImageLoadInfo+Private.h>
#if AS_PIN_REMOTE_IMAGE
#import <AsyncDisplayKit/ASPINRemoteImageDownloader.h>
@ -335,8 +336,9 @@
if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) {
ASDN::MutexLocker l(__instanceLock__);
if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) {
UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image];
NSURL *url = _URL;
if (_imageLoaded == NO && url && _downloadIdentifier == nil) {
UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:url] asdk_image];
if (result) {
[self _locked_setCurrentImageQuality:1.0];
[self _locked__setImage:result];
@ -345,8 +347,7 @@
// Call out to the delegate.
if (_delegateFlags.delegateDidLoadImageWithInfo) {
ASDN::MutexUnlocker l(__instanceLock__);
ASNetworkImageNodeDidLoadInfo info = {};
info.imageSource = ASNetworkImageSourceSynchronousCache;
auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:url sourceType:ASNetworkImageSourceSynchronousCache downloadIdentifier:nil userInfo:nil];
[_delegate imageNode:self didLoadImage:result info:info];
} else if (_delegateFlags.delegateDidLoadImage) {
ASDN::MutexUnlocker l(__instanceLock__);
@ -554,7 +555,7 @@
_cacheUUID = nil;
}
- (void)_downloadImageWithCompletion:(void (^)(id <ASImageContainerProtocol> imageContainer, NSError*, id downloadIdentifier))finished
- (void)_downloadImageWithCompletion:(void (^)(id <ASImageContainerProtocol> imageContainer, NSError*, id downloadIdentifier, id userInfo))finished
{
ASPerformBlockOnBackgroundThread(^{
NSURL *url;
@ -573,9 +574,9 @@
downloadIdentifier = [_downloader downloadImageWithURL:url
callbackQueue:dispatch_get_main_queue()
downloadProgress:NULL
completion:^(id <ASImageContainerProtocol> _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) {
completion:^(id <ASImageContainerProtocol> _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) {
if (finished != NULL) {
finished(imageContainer, error, downloadIdentifier);
finished(imageContainer, error, downloadIdentifier, userInfo);
}
}];
as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url);
@ -630,15 +631,15 @@
}
if (_shouldCacheImage) {
[self _locked__setImage:[UIImage imageNamed:_URL.path.lastPathComponent]];
[self _locked__setImage:[UIImage imageNamed:URL.path.lastPathComponent]];
} else {
// First try to load the path directly, for efficiency assuming a developer who
// doesn't want caching is trying to be as minimal as possible.
UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:_URL.path];
UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:URL.path];
if (nonAnimatedImage == nil) {
// If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the
// extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage.
NSString *filename = [[NSBundle mainBundle] pathForResource:_URL.path.lastPathComponent ofType:nil];
NSString *filename = [[NSBundle mainBundle] pathForResource:URL.path.lastPathComponent ofType:nil];
if (filename != nil) {
nonAnimatedImage = [UIImage imageWithContentsOfFile:filename];
}
@ -647,7 +648,7 @@
// If the file may be an animated gif and then created an animated image.
id<ASAnimatedImageProtocol> animatedImage = nil;
if (_downloaderFlags.downloaderImplementsAnimatedImage) {
NSData *data = [NSData dataWithContentsOfURL:_URL];
NSData *data = [NSData dataWithContentsOfURL:URL];
if (data != nil) {
animatedImage = [_downloader animatedImageWithData:data];
@ -670,8 +671,7 @@
if (_delegateFlags.delegateDidLoadImageWithInfo) {
ASDN::MutexUnlocker u(__instanceLock__);
ASNetworkImageNodeDidLoadInfo info = {};
info.imageSource = ASNetworkImageSourceFileURL;
auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:ASNetworkImageSourceFileURL downloadIdentifier:nil userInfo:nil];
[delegate imageNode:self didLoadImage:self.image info:info];
} else if (_delegateFlags.delegateDidLoadImage) {
ASDN::MutexUnlocker u(__instanceLock__);
@ -680,7 +680,7 @@
});
} else {
__weak __typeof__(self) weakSelf = self;
auto finished = ^(id <ASImageContainerProtocol>imageContainer, NSError *error, id downloadIdentifier, ASNetworkImageSource imageSource) {
auto finished = ^(id <ASImageContainerProtocol>imageContainer, NSError *error, id downloadIdentifier, ASNetworkImageSourceType imageSource, id userInfo) {
ASPerformBlockOnBackgroundThread(^{
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
@ -698,12 +698,13 @@
}
//No longer in preload range, no point in setting the results (they won't be cleared in exit preload range)
if (ASInterfaceStateIncludesPreload(self->_interfaceState) == NO) {
self->_downloadIdentifier = nil;
self->_cacheUUID = nil;
if (ASInterfaceStateIncludesPreload(strongSelf->_interfaceState) == NO) {
strongSelf->_downloadIdentifier = nil;
strongSelf->_cacheUUID = nil;
return;
}
UIImage *newImage;
if (imageContainer != nil) {
[strongSelf _locked_setCurrentImageQuality:1.0];
NSData *animatedImageData = [imageContainer asdk_animatedImageData];
@ -711,7 +712,8 @@
id animatedImage = [strongSelf->_downloader animatedImageWithData:animatedImageData];
[strongSelf _locked_setAnimatedImage:animatedImage];
} else {
[strongSelf _locked__setImage:[imageContainer asdk_image]];
newImage = [imageContainer asdk_image];
[strongSelf _locked__setImage:newImage];
}
strongSelf->_imageLoaded = YES;
}
@ -719,30 +721,32 @@
strongSelf->_downloadIdentifier = nil;
strongSelf->_cacheUUID = nil;
ASPerformBlockOnMainThread(^{
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
void (^calloutBlock)(ASNetworkImageNode *inst);
if (newImage) {
if (_delegateFlags.delegateDidLoadImageWithInfo) {
calloutBlock = ^(ASNetworkImageNode *strongSelf) {
auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:imageSource downloadIdentifier:downloadIdentifier userInfo:userInfo];
[delegate imageNode:strongSelf didLoadImage:newImage info:info];
};
} else if (_delegateFlags.delegateDidLoadImage) {
calloutBlock = ^(ASNetworkImageNode *strongSelf) {
[delegate imageNode:strongSelf didLoadImage:newImage];
};
}
// Grab the lock for the rest of the block
ASDN::MutexLocker l(strongSelf->__instanceLock__);
if (imageContainer != nil) {
if (strongSelf->_delegateFlags.delegateDidLoadImageWithInfo) {
ASDN::MutexUnlocker u(strongSelf->__instanceLock__);
ASNetworkImageNodeDidLoadInfo info = {};
info.imageSource = imageSource;
[delegate imageNode:strongSelf didLoadImage:strongSelf.image info:info];
} else if (strongSelf->_delegateFlags.delegateDidLoadImage) {
ASDN::MutexUnlocker u(strongSelf->__instanceLock__);
[delegate imageNode:strongSelf didLoadImage:strongSelf.image];
}
} else if (error && strongSelf->_delegateFlags.delegateDidFailWithError) {
ASDN::MutexUnlocker u(strongSelf->__instanceLock__);
} else if (error && _delegateFlags.delegateDidFailWithError) {
calloutBlock = ^(ASNetworkImageNode *strongSelf) {
[delegate imageNode:strongSelf didFailWithError:error];
}
});
};
}
if (calloutBlock) {
ASPerformBlockOnMainThread(^{
if (auto strongSelf = weakSelf) {
calloutBlock(strongSelf);
}
});
}
});
};
@ -767,20 +771,20 @@
}
if ([imageContainer asdk_image] == nil && _downloader != nil) {
[self _downloadImageWithCompletion:^(id<ASImageContainerProtocol> imageContainer, NSError *error, id downloadIdentifier) {
finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload);
[self _downloadImageWithCompletion:^(id<ASImageContainerProtocol> imageContainer, NSError *error, id downloadIdentifier, id userInfo) {
finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload, userInfo);
}];
} else {
as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL);
finished(imageContainer, nil, nil, ASNetworkImageSourceAsynchronousCache);
finished(imageContainer, nil, nil, ASNetworkImageSourceAsynchronousCache, nil);
}
};
[_cache cachedImageWithURL:URL
callbackQueue:dispatch_get_main_queue()
completion:completion];
} else {
[self _downloadImageWithCompletion:^(id<ASImageContainerProtocol> imageContainer, NSError *error, id downloadIdentifier) {
finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload);
[self _downloadImageWithCompletion:^(id<ASImageContainerProtocol> imageContainer, NSError *error, id downloadIdentifier, id userInfo) {
finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload, userInfo);
}];
}
}

View File

@ -20,8 +20,15 @@
NS_ASSUME_NONNULL_BEGIN
@protocol ASCATransactionQueueObserving <NSObject>
- (void)prepareForCATransactionCommit;
@end
@interface ASAbstractRunLoopQueue : NSObject
@end
AS_SUBCLASSING_RESTRICTED
@interface ASRunLoopQueue<ObjectType> : NSObject <NSLocking>
@interface ASRunLoopQueue<ObjectType> : ASAbstractRunLoopQueue <NSLocking>
/**
* Create a new queue with the given run loop and handler.
@ -41,13 +48,37 @@ AS_SUBCLASSING_RESTRICTED
- (void)enqueue:(ObjectType)object;
@property (nonatomic, readonly) BOOL isEmpty;
@property (atomic, readonly) BOOL isEmpty;
@property (nonatomic, assign) NSUInteger batchSize; // Default == 1.
@property (nonatomic, assign) BOOL ensureExclusiveMembership; // Default == YES. Set-like behavior.
@end
AS_SUBCLASSING_RESTRICTED
@interface ASCATransactionQueue : ASAbstractRunLoopQueue
@property (atomic, readonly) BOOL isEmpty;
@property (atomic, readonly) BOOL disabled;
/**
* The queue to run on main run loop before CATransaction commit.
*
* @discussion this queue will run after ASRunLoopQueue and before CATransaction commit
* to get last chance of updating/coalesce info like interface state.
* Each node will only be called once per transaction commit to reflect interface change.
*/
@property (class, atomic, readonly) ASCATransactionQueue *sharedQueue;
- (void)enqueue:(id<ASCATransactionQueueObserving>)object;
/**
* @abstract Apply a node's interfaceState immediately rather than adding to the queue.
*/
- (void)disable;
@end
AS_SUBCLASSING_RESTRICTED
@interface ASDeallocQueue : NSObject

View File

@ -57,11 +57,6 @@ static void runLoopSourceCallback(void *info) {
- (void)releaseObjectInBackground:(id _Nullable __strong *)objectPtr
{
// Disable background deallocation on iOS 8 and below to avoid crashes related to UIAXDelegateClearer (#2767).
if (!AS_AT_LEAST_IOS9) {
return;
}
if (objectPtr != NULL && *objectPtr != nil) {
ASDN::MutexLocker l(_queueLock);
_queue.push_back(*objectPtr);
@ -186,27 +181,6 @@ static void runLoopSourceCallback(void *info) {
@end
#pragma mark - ASRunLoopQueue
@interface ASRunLoopQueue () {
CFRunLoopRef _runLoop;
CFRunLoopSourceRef _runLoopSource;
CFRunLoopObserverRef _runLoopObserver;
NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance.
ASDN::RecursiveMutex _internalQueueLock;
// In order to not pollute the top-level activities, each queue has 1 root activity.
os_activity_t _rootActivity;
#if ASRunLoopQueueLoggingEnabled
NSTimer *_runloopQueueLoggingTimer;
#endif
}
@property (nonatomic, copy) void (^queueConsumer)(id dequeuedItem, BOOL isQueueDrained);
@end
#if AS_KDEBUG_ENABLE
/**
* This is real, private CA API. Valid as of iOS 10.
@ -223,7 +197,23 @@ typedef enum {
@end
#endif
@implementation ASRunLoopQueue
#pragma mark - ASAbstractRunLoopQueue
@interface ASAbstractRunLoopQueue (Private)
+ (void)load;
+ (void)registerCATransactionObservers;
@end
@implementation ASAbstractRunLoopQueue
- (instancetype)init
{
if (self != [super init]) {
return nil;
}
ASDisplayNodeAssert(self.class != [ASAbstractRunLoopQueue class], @"Should never create instances of abstract class ASAbstractRunLoopQueue.");
return self;
}
#if AS_KDEBUG_ENABLE
+ (void)load
@ -270,6 +260,31 @@ typedef enum {
#endif // AS_KDEBUG_ENABLE
@end
#pragma mark - ASRunLoopQueue
@interface ASRunLoopQueue () {
CFRunLoopRef _runLoop;
CFRunLoopSourceRef _runLoopSource;
CFRunLoopObserverRef _runLoopObserver;
NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance.
ASDN::RecursiveMutex _internalQueueLock;
// In order to not pollute the top-level activities, each queue has 1 root activity.
os_activity_t _rootActivity;
#if ASRunLoopQueueLoggingEnabled
NSTimer *_runloopQueueLoggingTimer;
#endif
}
@property (nonatomic, copy) void (^queueConsumer)(id dequeuedItem, BOOL isQueueDrained);
@end
@implementation ASRunLoopQueue
- (instancetype)initWithRunLoop:(CFRunLoopRef)runloop retainObjects:(BOOL)retainsObjects handler:(void (^)(id _Nullable, BOOL))handlerBlock
{
if (self = [super init]) {
@ -469,3 +484,232 @@ typedef enum {
}
@end
#pragma mark - ASCATransactionQueue
@interface ASCATransactionQueue () {
CFRunLoopRef _runLoop;
CFRunLoopSourceRef _runLoopSource;
CFRunLoopObserverRef _preTransactionObserver;
CFRunLoopObserverRef _postTransactionObserver;
NSPointerArray *_internalQueue;
ASDN::RecursiveMutex _internalQueueLock;
BOOL _disableInterfaceStateCoalesce;
BOOL _CATransactionCommitInProgress;
// In order to not pollute the top-level activities, each queue has 1 root activity.
os_activity_t _rootActivity;
#if ASRunLoopQueueLoggingEnabled
NSTimer *_runloopQueueLoggingTimer;
#endif
}
@end
@implementation ASCATransactionQueue
// CoreAnimation commit order is 2000000, the goal of this is to process shortly beforehand
// but after most other scheduled work on the runloop has processed.
static int const kASASCATransactionQueueOrder = 1000000;
// This will mark the end of current loop and any node enqueued between kASASCATransactionQueueOrder
// and kASASCATransactionQueuePostOrder will apply interface change immediately.
static int const kASASCATransactionQueuePostOrder = 3000000;
+ (ASCATransactionQueue *)sharedQueue
{
static dispatch_once_t onceToken;
static ASCATransactionQueue *sharedQueue;
dispatch_once(&onceToken, ^{
sharedQueue = [[ASCATransactionQueue alloc] init];
});
return sharedQueue;
}
- (instancetype)init
{
if (self = [super init]) {
_runLoop = CFRunLoopGetMain();
NSPointerFunctionsOptions options = NSPointerFunctionsStrongMemory;
_internalQueue = [[NSPointerArray alloc] initWithOptions:options];
// We don't want to pollute the top-level app activities with run loop batches, so we create one top-level
// activity per queue, and each batch activity joins that one instead.
_rootActivity = as_activity_create("Process run loop queue items", OS_ACTIVITY_NONE, OS_ACTIVITY_FLAG_DEFAULT);
{
// Log a message identifying this queue into the queue's root activity.
as_activity_scope_verbose(_rootActivity);
as_log_verbose(ASDisplayLog(), "Created run loop queue: %@", self);
}
// Self is guaranteed to outlive the observer. Without the high cost of a weak pointer,
// __unsafe_unretained allows us to avoid flagging the memory cycle detector.
__unsafe_unretained __typeof__(self) weakSelf = self;
void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
[weakSelf processQueue];
};
void (^postHandlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
ASDN::MutexLocker l(_internalQueueLock);
_CATransactionCommitInProgress = NO;
};
_preTransactionObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, kASASCATransactionQueueOrder, handlerBlock);
_postTransactionObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, kASASCATransactionQueuePostOrder, postHandlerBlock);
CFRunLoopAddObserver(_runLoop, _preTransactionObserver, kCFRunLoopCommonModes);
CFRunLoopAddObserver(_runLoop, _postTransactionObserver, kCFRunLoopCommonModes);
// It is not guaranteed that the runloop will turn if it has no scheduled work, and this causes processing of
// the queue to stop. Attaching a custom loop source to the run loop and signal it if new work needs to be done
CFRunLoopSourceContext sourceContext = {};
sourceContext.perform = runLoopSourceCallback;
#if ASRunLoopQueueLoggingEnabled
sourceContext.info = (__bridge void *)self;
#endif
_runLoopSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext);
CFRunLoopAddSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes);
#if ASRunLoopQueueLoggingEnabled
_runloopQueueLoggingTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(checkRunLoop) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_runloopQueueLoggingTimer forMode:NSRunLoopCommonModes];
#endif
}
return self;
}
- (void)dealloc
{
CFRunLoopRemoveSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes);
CFRelease(_runLoopSource);
_runLoopSource = nil;
if (CFRunLoopObserverIsValid(_preTransactionObserver)) {
CFRunLoopObserverInvalidate(_preTransactionObserver);
}
if (CFRunLoopObserverIsValid(_postTransactionObserver)) {
CFRunLoopObserverInvalidate(_postTransactionObserver);
}
CFRelease(_preTransactionObserver);
CFRelease(_postTransactionObserver);
_preTransactionObserver = nil;
_postTransactionObserver = nil;
}
#if ASRunLoopQueueLoggingEnabled
- (void)checkRunLoop
{
NSLog(@"<%@> - Jobs: %ld", self, _internalQueue.size());
}
#endif
- (void)processQueue
{
// If we have an execution block, this vector will be populated, otherwise remains empty.
// This is to avoid needlessly retaining/releasing the objects if we don't have a block.
std::vector<id> itemsToProcess;
{
ASDN::MutexLocker l(_internalQueueLock);
// Mark the queue will end coalescing shortly until after CATransactionCommit.
// This will give the queue a chance to apply any further interfaceState changes/enqueue
// immediately within current runloop instead of pushing the work to next runloop cycle.
_CATransactionCommitInProgress = YES;
NSInteger internalQueueCount = _internalQueue.count;
// Early-exit if the queue is empty.
if (internalQueueCount == 0) {
return;
}
ASSignpostStart(ASSignpostRunLoopQueueBatch);
/**
* For each item in the next batch, if it's non-nil then NULL it out
* and if we have an execution block then add it in.
* This could be written a bunch of different ways but
* this particular one nicely balances readability, safety, and efficiency.
*/
NSInteger foundItemCount = 0;
for (NSInteger i = 0; i < internalQueueCount && foundItemCount < internalQueueCount; i++) {
/**
* It is safe to use unsafe_unretained here. If the queue is weak, the
* object will be added to the autorelease pool. If the queue is strong,
* it will retain the object until we transfer it (retain it) in itemsToProcess.
*/
__unsafe_unretained id ptr = (__bridge id)[_internalQueue pointerAtIndex:i];
if (ptr != nil) {
foundItemCount++;
itemsToProcess.push_back(ptr);
[_internalQueue replacePointerAtIndex:i withPointer:NULL];
}
}
[_internalQueue compact];
}
// itemsToProcess will be empty if _queueConsumer == nil so no need to check again.
auto count = itemsToProcess.size();
if (count > 0) {
as_activity_scope_verbose(as_activity_create("Process run loop queue batch", _rootActivity, OS_ACTIVITY_FLAG_DEFAULT));
auto itemsEnd = itemsToProcess.cend();
for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) {
__unsafe_unretained id value = *iterator;
[value prepareForCATransactionCommit];
as_log_verbose(ASDisplayLog(), "processed %@", value);
}
if (count > 1) {
as_log_verbose(ASDisplayLog(), "processed %lu items", (unsigned long)count);
}
}
ASSignpostEnd(ASSignpostRunLoopQueueBatch);
}
- (void)enqueue:(id<ASCATransactionQueueObserving>)object
{
if (!object) {
return;
}
if (_disableInterfaceStateCoalesce || _CATransactionCommitInProgress) {
[object prepareForCATransactionCommit];
return;
}
ASDN::MutexLocker l(_internalQueueLock);
// Check if the object exists.
BOOL foundObject = NO;
for (id currentObject in _internalQueue) {
if (currentObject == object) {
foundObject = YES;
break;
}
}
if (!foundObject) {
[_internalQueue addPointer:(__bridge void *)object];
CFRunLoopSourceSignal(_runLoopSource);
CFRunLoopWakeUp(_runLoop);
}
}
- (BOOL)isEmpty
{
ASDN::MutexLocker l(_internalQueueLock);
return _internalQueue.count == 0;
}
- (void)disable
{
_disableInterfaceStateCoalesce = YES;
}
- (BOOL)disabled
{
return _disableInterfaceStateCoalesce;
}
@end

View File

@ -105,6 +105,18 @@
return [self initWithStyle:UITableViewStylePlain];
}
#if ASDISPLAYNODE_ASSERTIONS_ENABLED
- (void)dealloc
{
if (self.nodeLoaded) {
__weak UIView *view = self.view;
ASPerformBlockOnMainThread(^{
ASDisplayNodeCAssertNil(view.superview, @"Node's view should be removed from hierarchy.");
});
}
}
#endif
#pragma mark ASDisplayNode
- (void)didLoad

View File

@ -115,11 +115,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
if (node) {
self.backgroundColor = node.backgroundColor;
self.selectionStyle = node.selectionStyle;
self.selectedBackgroundView = node.selectedBackgroundView;
self.separatorInset = node.separatorInset;
self.selectionStyle = node.selectionStyle;
self.selectionStyle = node.selectionStyle;
self.focusStyle = node.focusStyle;
self.accessoryType = node.accessoryType;
self.tintColor = node.tintColor;
// the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default)
// This is actually a workaround for a bug we are seeing in some rare cases (selected background view
@ -188,15 +189,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
BOOL _automaticallyAdjustsContentOffset;
CGPoint _deceleratingVelocity;
/**
* Our layer, retained. Under iOS < 9, when table views are removed from the hierarchy,
* their layers may be deallocated and become dangling pointers. This puts the table view
* into a very dangerous state where pretty much any call will crash it. So we manually retain our layer.
*
* You should never access this, and it will be nil under iOS >= 9.
*/
CALayer *_retainedLayer;
CGFloat _nodesConstrainedWidth;
BOOL _queuedNodeHeightUpdate;
@ -225,7 +217,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
* Counter used to keep track of nested batch updates.
*/
NSInteger _batchUpdateCount;
/**
* Keep a strong reference to node till view is ready to release.
*/
ASTableNode *_keepalive_node;
struct {
unsigned int scrollViewDidScroll:1;
unsigned int scrollViewWillBeginDragging:1;
@ -351,10 +348,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
[self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier];
if (!AS_AT_LEAST_IOS9) {
_retainedLayer = self.layer;
}
// iOS 11 automatically uses estimated heights, so disable those (see PR #485)
if (AS_AT_LEAST_IOS11) {
super.estimatedRowHeight = 0.0;
@ -1227,13 +1220,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
[super scrollViewDidScroll:scrollView];
return;
}
// If a scroll happenes the current range mode needs to go to full
ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController];
if (ASInterfaceStateIncludesVisible(interfaceState)) {
[_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull];
[self _checkForBatchFetching];
}
}
for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) {
[[tableCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged
inScrollView:scrollView
@ -1285,6 +1275,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
[super scrollViewWillBeginDragging:scrollView];
return;
}
// If a scroll happens the current range mode needs to go to full
_rangeController.contentHasBeenScrolled = YES;
[_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull];
for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) {
[[tableViewCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging
inScrollView:scrollView
@ -1925,6 +1919,20 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
}
}
- (void)willMoveToSuperview:(UIView *)newSuperview
{
if (self.superview == nil && newSuperview != nil) {
_keepalive_node = self.tableNode;
}
}
- (void)didMoveToSuperview
{
if (self.superview == nil) {
_keepalive_node = nil;
}
}
@end
#endif

View File

@ -28,6 +28,7 @@
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASTextKitCoreTextAdditions.h>
#import <AsyncDisplayKit/ASTextKitRenderer+Positioning.h>
@ -229,6 +230,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
{
CGColorRelease(_shadowColor);
// TODO: This may not be necessary post-iOS-9 when most UIKit assign APIs
// were changed to weak.
if (_longPressGestureRecognizer) {
_longPressGestureRecognizer.delegate = nil;
[_longPressGestureRecognizer removeTarget:nil action:NULL];
@ -907,7 +910,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
ASDN::MutexLocker l(__instanceLock__);
UIGraphicsBeginImageContext(size);
ASGraphicsBeginImageContextWithOptions(size, NO, 1.0);
[self.placeholderColor setFill];
ASTextKitRenderer *renderer = [self _locked_renderer];
@ -926,8 +929,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
}
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
return image;
}

View File

@ -232,7 +232,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
[self _ensureTruncationText];
NSMutableAttributedString *mutableText = [attributedText mutableCopy];
[self prepareAttributedStringForDrawing:mutableText];
[self prepareAttributedString:mutableText];
ASTextLayout *layout = [ASTextNode2 compatibleLayoutWithContainer:container text:mutableText];
[self setNeedsDisplay];
@ -319,7 +319,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
return _textContainer.exclusionPaths;
}
- (void)prepareAttributedStringForDrawing:(NSMutableAttributedString *)attributedString
- (void)prepareAttributedString:(NSMutableAttributedString *)attributedString
{
ASDN::MutexLocker lock(__instanceLock__);
@ -334,12 +334,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
}];
// Apply background color if needed
UIColor *backgroundColor = self.backgroundColor;
if (CGColorGetAlpha(backgroundColor.CGColor) > 0) {
[attributedString addAttribute:NSBackgroundColorAttributeName value:backgroundColor range:NSMakeRange(0, attributedString.length)];
}
// Apply shadow if needed
if (_shadowOpacity > 0 && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero)) && CGColorGetAlpha(_shadowColor) > 0) {
NSShadow *shadow = [[NSShadow alloc] init];
@ -362,11 +356,19 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
ASTextContainer *copiedContainer = [_textContainer copy];
copiedContainer.size = self.bounds.size;
NSMutableAttributedString *mutableText = [self.attributedText mutableCopy] ?: [[NSMutableAttributedString alloc] init];
[self prepareAttributedStringForDrawing:mutableText];
[self prepareAttributedString:mutableText];
// Apply background color if needed before drawing. To access the backgroundColor we need to be on the main thread
UIColor *backgroundColor = self.backgroundColor;
if (CGColorGetAlpha(backgroundColor.CGColor) > 0) {
[mutableText addAttribute:NSBackgroundColorAttributeName value:backgroundColor range:NSMakeRange(0, mutableText.length)];
}
return @{
@"container": copiedContainer,
@"text": mutableText
};
@"container": copiedContainer,
@"text": mutableText
};
}
/**

View File

@ -35,6 +35,7 @@
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
#import <AsyncDisplayKit/ASPINRemoteImageDownloader.h>
#import <AsyncDisplayKit/ASMultiplexImageNode.h>
#import <AsyncDisplayKit/ASNetworkImageLoadInfo.h>
#import <AsyncDisplayKit/ASNetworkImageNode.h>
#import <AsyncDisplayKit/ASPhotosFrameworkImageRequest.h>
@ -119,6 +120,7 @@
#import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h>
#import <AsyncDisplayKit/UIView+ASConvenience.h>
#import <AsyncDisplayKit/UIImage+ASConvenience.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/NSArray+Diffing.h>
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
#import <AsyncDisplayKit/UIResponder+AsyncDisplayKit.h>

View File

@ -19,10 +19,6 @@
#pragma once
#ifndef kCFCoreFoundationVersionNumber_iOS_9_0
#define kCFCoreFoundationVersionNumber_iOS_9_0 1240.10
#endif
#ifndef kCFCoreFoundationVersionNumber_iOS_10_0
#define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00
#endif
@ -35,7 +31,6 @@
#define __IPHONE_11_0 110000
#endif
#define AS_AT_LEAST_IOS9 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0)
#define AS_AT_LEAST_IOS10 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0)
#define AS_AT_LEAST_IOS11 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_11_0)

View File

@ -17,6 +17,7 @@
#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
#import <AsyncDisplayKit/ASAbstractLayoutController.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASWeakSet.h>
#import <AsyncDisplayKit/UIImage+ASConvenience.h>
@ -148,7 +149,7 @@ static BOOL __enableHitTestDebug = NO;
UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7];
CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0);
UIGraphicsBeginImageContext(imgRect.size);
ASGraphicsBeginImageContextWithOptions(imgRect.size, NO, 1);
[fillColor setFill];
UIRectFill(imgRect);
@ -156,8 +157,7 @@ static BOOL __enableHitTestDebug = NO;
[self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect];
[self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect];
UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *debugHighlightImage = ASGraphicsGetImageAndEndCurrentContext();
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth);
debugOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets resizingMode:UIImageResizingModeStretch];

View File

@ -27,14 +27,15 @@ NS_ASSUME_NONNULL_BEGIN
@interface ASBasicImageDownloader : NSObject <ASImageDownloaderProtocol>
/**
* A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes
* A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes.
* The userInfo provided by this downloader is `nil`.
*
* This is a very basic image downloader. It does not support caching, progressive downloading and likely
* isn't something you should use in production. If you'd like something production ready, see @c ASPINRemoteImageDownloader
*
* @note It is strongly recommended you include PINRemoteImage and use @c ASPINRemoteImageDownloader instead.
*/
+ (instancetype)sharedImageDownloader;
@property (class, readonly) ASBasicImageDownloader *sharedImageDownloader;
+ (instancetype)new __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used.")));
- (instancetype)init __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used.")));

View File

@ -130,7 +130,7 @@ static ASDN::StaticMutex& currentRequestsLock = *new ASDN::StaticMutex;
if (completionBlock) {
dispatch_async(callbackQueue, ^{
completionBlock(image, error, nil);
completionBlock(image, error, nil, nil);
});
}
}
@ -206,7 +206,7 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext
@implementation ASBasicImageDownloader
+ (instancetype)sharedImageDownloader
+ (ASBasicImageDownloader *)sharedImageDownloader
{
static ASBasicImageDownloader *sharedImageDownloader = nil;
static dispatch_once_t once = 0;
@ -235,9 +235,9 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext
#pragma mark ASImageDownloaderProtocol.
- (id)downloadImageWithURL:(NSURL *)URL
callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress
completion:(ASImageDownloaderCompletion)completion
callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress
completion:(ASImageDownloaderCompletion)completion
{
ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:URL];

View File

@ -528,7 +528,7 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
BOOL canDelegate = (self.layoutDelegate != nil);
ASElementMap *newMap;
id layoutContext;
ASCollectionLayoutContext *layoutContext;
{
as_activity_scope(as_activity_create("Latch new data for collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT));

View File

@ -0,0 +1,65 @@
//
// ASGraphicsContext.h
// Texture
//
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <CoreGraphics/CoreGraphics.h>
@class UIImage;
/**
* Functions for creating one-shot graphics contexts that do not have to copy
* their contents when an image is generated from them. This is efficient
* for our use, since we do not reuse graphics contexts.
*
* The API mirrors the UIGraphics API, with the exception that forming an image
* ends the context as well.
*
* Note: You must not mix-and-match between ASGraphics* and UIGraphics* functions
* within the same drawing operation.
*/
NS_ASSUME_NONNULL_BEGIN
ASDISPLAYNODE_EXTERN_C_BEGIN
/**
* Call this to enable the experimental no-copy rendering.
*
* Returns YES if it was enabled, or NO + assert if it's too late because
* rendering has already started. In practice it's fine to call this
* during -didFinishLaunchingWithOptions:.
*/
extern BOOL ASEnableNoCopyRendering(void);
/**
* Creates a one-shot context.
*
* Behavior is the same as UIGraphicsBeginImageContextWithOptions.
*/
extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);
/**
* Generates and image and ends the current one-shot context.
*
* Behavior is the same as UIGraphicsGetImageFromCurrentImageContext followed by UIGraphicsEndImageContext.
*/
extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext(void);
/**
* Call this if you want to end the current context without making an image.
*
* Behavior is the same as UIGraphicsEndImageContext.
*/
extern void ASGraphicsEndImageContext(void);
ASDISPLAYNODE_EXTERN_C_END
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,194 @@
//
// ASGraphicsContext.m
// Texture
//
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import "ASGraphicsContext.h"
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <UIKit/UIGraphics.h>
#import <UIKit/UIImage.h>
#import <stdatomic.h>
#import <objc/runtime.h>
#pragma mark - Feature Gating
// Two flags that we atomically manipulate to control the feature.
typedef NS_OPTIONS(uint, ASNoCopyFlags) {
ASNoCopyEnabled = 1 << 0,
ASNoCopyBlocked = 1 << 1
};
static atomic_uint __noCopyFlags;
// Check if it's blocked, and set the enabled flag if not.
extern BOOL ASEnableNoCopyRendering()
{
ASNoCopyFlags expectedFlags = 0;
BOOL enabled = atomic_compare_exchange_strong(&__noCopyFlags, &expectedFlags, ASNoCopyEnabled);
ASDisplayNodeCAssert(enabled, @"Can't enable no-copy rendering after first render started.");
return enabled;
}
// Check if it's enabled and set the "blocked" flag either way.
static BOOL ASNoCopyRenderingBlockAndCheckEnabled() {
ASNoCopyFlags oldFlags = atomic_fetch_or(&__noCopyFlags, ASNoCopyBlocked);
return (oldFlags & ASNoCopyEnabled) != 0;
}
/**
* Our version of the private CGBitmapGetAlignedBytesPerRow function.
*
* In both 32-bit and 64-bit, this function rounds up to nearest multiple of 32
* in iOS 9, 10, and 11. We'll try to catch if this ever changes by asserting that
* the bytes-per-row for a 1x1 context from the system is 32.
*/
static size_t ASGraphicsGetAlignedBytesPerRow(size_t baseValue) {
// Add 31 then zero out low 5 bits.
return (baseValue + 31) & ~0x1F;
}
/**
* A key used to associate CGContextRef -> NSMutableData, nonatomic retain.
*
* That way the data will be released when the context dies. If they pull an image,
* we will retain the data object (in a CGDataProvider) before releasing the context.
*/
static UInt8 __contextDataAssociationKey;
#pragma mark - Graphics Contexts
extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
{
if (!ASNoCopyRenderingBlockAndCheckEnabled()) {
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
return;
}
// We use "reference contexts" to get device-specific options that UIKit
// uses.
static dispatch_once_t onceToken;
static CGContextRef refCtxOpaque;
static CGContextRef refCtxTransparent;
dispatch_once(&onceToken, ^{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 1);
refCtxOpaque = CGContextRetain(UIGraphicsGetCurrentContext());
ASDisplayNodeCAssert(CGBitmapContextGetBytesPerRow(refCtxOpaque) == 32, @"Expected bytes per row to be aligned to 32. Has CGBitmapGetAlignedBytesPerRow implementation changed?");
UIGraphicsEndImageContext();
// Make transparent ref context.
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1);
refCtxTransparent = CGContextRetain(UIGraphicsGetCurrentContext());
UIGraphicsEndImageContext();
});
// These options are taken from UIGraphicsBeginImageContext.
CGContextRef refCtx = opaque ? refCtxOpaque : refCtxTransparent;
CGBitmapInfo bitmapInfo = CGBitmapContextGetBitmapInfo(refCtx);
if (scale == 0) {
scale = ASScreenScale();
}
size_t intWidth = (size_t)ceil(size.width * scale);
size_t intHeight = (size_t)ceil(size.height * scale);
size_t bitsPerComponent = CGBitmapContextGetBitsPerComponent(refCtx);
size_t bytesPerRow = CGBitmapContextGetBitsPerPixel(refCtx) * intWidth / 8;
bytesPerRow = ASGraphicsGetAlignedBytesPerRow(bytesPerRow);
size_t bufferSize = bytesPerRow * intHeight;
CGColorSpaceRef colorspace = CGBitmapContextGetColorSpace(refCtx);
// We create our own buffer, and wrap the context around that. This way we can prevent
// the copy that usually gets made when you form a CGImage from the context.
NSMutableData *data = [[NSMutableData alloc] initWithLength:bufferSize];
CGContextRef context = CGBitmapContextCreate(data.mutableBytes, intWidth, intHeight, bitsPerComponent, bytesPerRow, colorspace, bitmapInfo);
// Transfer ownership of the data to the context. So that if the context
// is destroyed before we create an image from it, the data will be released.
objc_setAssociatedObject((__bridge id)context, &__contextDataAssociationKey, data, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// Set the CTM to account for iOS orientation & specified scale.
// If only we could use CGContextSetBaseCTM. It doesn't
// seem like there are any consequences for our use case
// but we'll be on the look out. The internet hinted that it
// affects shadowing but I tested and shadowing works.
CGContextTranslateCTM(context, 0, intHeight);
CGContextScaleCTM(context, scale, -scale);
// Save the state so we can restore it and recover our scale in GetImageAndEnd
CGContextSaveGState(context);
// Transfer context ownership to the UIKit stack.
UIGraphicsPushContext(context);
CGContextRelease(context);
}
extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext()
{
if (!ASNoCopyRenderingBlockAndCheckEnabled()) {
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
// Pop the context and make sure we have one.
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) {
ASDisplayNodeCFailAssert(@"Can't end image context without having begun one.");
return nil;
}
// Read the device-specific ICC-based color space to use for the image.
// For DeviceRGB contexts (e.g. UIGraphics), CGBitmapContextCreateImage
// generates an image in a device-specific color space (for wide color support).
// We replicate that behavior, even though at this time CA does not
// require the image to be in this space. Plain DeviceRGB images seem
// to be treated exactly the same, but better safe than sorry.
static CGColorSpaceRef imageColorSpace;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0);
UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext();
imageColorSpace = CGColorSpaceRetain(CGImageGetColorSpace(refImage.CGImage));
ASDisplayNodeCAssertNotNil(imageColorSpace, nil);
UIGraphicsEndImageContext();
});
// Retrieve our data and wrap it in a CGDataProvider.
// Don't worry, the provider doesn't copy the data it just retains it.
NSMutableData *data = objc_getAssociatedObject((__bridge id)context, &__contextDataAssociationKey);
ASDisplayNodeCAssertNotNil(data, nil);
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
// Create the CGImage. Options taken from CGBitmapContextCreateImage.
CGImageRef cgImg = CGImageCreate(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context), CGBitmapContextGetBitsPerComponent(context), CGBitmapContextGetBitsPerPixel(context), CGBitmapContextGetBytesPerRow(context), imageColorSpace, CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(provider);
// We saved our GState right after setting the CTM so that we could restore it
// here and get the original scale back.
CGContextRestoreGState(context);
CGFloat scale = CGContextGetCTM(context).a;
// Note: popping from the UIKit stack will probably destroy the context.
context = NULL;
UIGraphicsPopContext();
UIImage *result = [[UIImage alloc] initWithCGImage:cgImg scale:scale orientation:UIImageOrientationUp];
CGImageRelease(cgImg);
return result;
}
extern void ASGraphicsEndImageContext()
{
if (!ASNoCopyRenderingBlockAndCheckEnabled()) {
UIGraphicsEndImageContext();
return;
}
UIGraphicsPopContext();
}

View File

@ -72,8 +72,9 @@ typedef void(^ASImageCacherCompletion)(id <ASImageContainerProtocol> _Nullable i
@param image The image that was downloaded, if the image could be successfully downloaded; nil otherwise.
@param error An error describing why the download of `URL` failed, if the download failed; nil otherwise.
@param downloadIdentifier The identifier for the download task that completed.
@param userInfo Any additional info that your downloader would like to communicate through Texture.
*/
typedef void(^ASImageDownloaderCompletion)(id <ASImageContainerProtocol> _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier);
typedef void(^ASImageDownloaderCompletion)(id <ASImageContainerProtocol> _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo);
/**
@param progress The progress of the download, in the range of (0.0, 1.0), inclusive.

View File

@ -30,12 +30,13 @@ NS_ASSUME_NONNULL_BEGIN
@interface ASPINRemoteImageDownloader : NSObject <ASImageCacheProtocol, ASImageDownloaderProtocol>
/**
* A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes
* A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes.
* The userInfo provided by this downloader is an instance of `PINRemoteImageManagerResult`.
*
* This is the default downloader used by network backed image nodes if PINRemoteImage and PINCache are
* available. It uses PINRemoteImage's features to provide caching and progressive image downloads.
*/
+ (ASPINRemoteImageDownloader *)sharedDownloader;
@property (class, readonly) ASPINRemoteImageDownloader *sharedDownloader;
/**

View File

@ -116,7 +116,7 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil;
@implementation ASPINRemoteImageDownloader
+ (instancetype)sharedDownloader
+ (ASPINRemoteImageDownloader *)sharedDownloader
{
static dispatch_once_t onceToken = 0;
@ -237,12 +237,12 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil;
[ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{
#if PIN_ANIMATED_AVAILABLE
if (result.alternativeRepresentation) {
completion(result.alternativeRepresentation, result.error, result.UUID);
completion(result.alternativeRepresentation, result.error, result.UUID, result);
} else {
completion(result.image, result.error, result.UUID);
completion(result.image, result.error, result.UUID, result);
}
#else
completion(result.image, result.error, result.UUID);
completion(result.image, result.error, result.UUID, result);
#endif
}];
};

View File

@ -102,6 +102,11 @@ AS_SUBCLASSING_RESTRICTED
*/
@property (nonatomic, weak) id<ASRangeControllerDelegate> delegate;
/**
* Property that indicates whether the scroll view for this range controller has ever changed its contentOffset.
*/
@property (nonatomic, assign) BOOL contentHasBeenScrolled;
@end

View File

@ -46,6 +46,7 @@
NSSet<NSIndexPath *> *_allPreviousIndexPaths;
NSHashTable<ASCellNode *> *_visibleNodes;
ASLayoutRangeMode _currentRangeMode;
BOOL _contentHasBeenScrolled;
BOOL _preserveCurrentRangeMode;
BOOL _didRegisterForNodeDisplayNotifications;
CFTimeInterval _pendingDisplayNodesTimestamp;
@ -78,6 +79,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
_rangeIsValid = YES;
_currentRangeMode = ASLayoutRangeModeUnspecified;
_contentHasBeenScrolled = NO;
_preserveCurrentRangeMode = NO;
_previousScrollDirection = ASScrollDirectionDown | ASScrollDirectionRight;
@ -223,10 +225,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
auto visibleElements = [_dataSource visibleElementsForRangeController:self];
NSHashTable *newVisibleNodes = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality];
if (visibleElements.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)...
[self _setVisibleNodes:newVisibleNodes];
return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later
}
ASSignpostStart(ASSignpostRangeControllerUpdate);
// Get the scroll direction. Default to using the previous one, if they're not scrolling.
@ -235,12 +233,28 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
scrollDirection = _previousScrollDirection;
}
_previousScrollDirection = scrollDirection;
if (visibleElements.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)...
// Verify the actual state by checking the layout with a "VisibleOnly" range.
// This allows us to avoid thrashing through -didExitVisibleState in the case of -reloadData, since that generates didEndDisplayingCell calls.
// Those didEndDisplayingCell calls result in items being removed from the visibleElements returned by the _dataSource, even though the layout remains correct.
visibleElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:ASLayoutRangeModeVisibleOnly rangeType:ASLayoutRangeTypeDisplay map:map];
for (ASCollectionElement *element in visibleElements) {
[newVisibleNodes addObject:element.node];
}
[self _setVisibleNodes:newVisibleNodes];
return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later
}
ASInterfaceState selfInterfaceState = [self interfaceState];
ASLayoutRangeMode rangeMode = _currentRangeMode;
// If the range mode is explicitly set via updateCurrentRangeWithMode: it will last in that mode until the
// range controller becomes visible again or explicitly changes the range mode again
if ((!_preserveCurrentRangeMode && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) {
BOOL updateRangeMode = (!_preserveCurrentRangeMode && _contentHasBeenScrolled);
// If we've never scrolled before, we never update the range mode, so it doesn't jump into Full too early.
// This can happen if we have multiple, noisy updates occurring from application code before the user has engaged.
// If the range mode is explicitly set via updateCurrentRangeWithMode:, we'll preserve that for at least one update cycle.
// Once the user has scrolled and the range is visible, we'll always resume managing the range mode automatically.
if ((updateRangeMode && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) {
rangeMode = [ASRangeController rangeModeForInterfaceState:selfInterfaceState currentRangeMode:_currentRangeMode];
}
@ -413,7 +427,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
// NSLog(@"custom: %@", visibleNodePathsSet);
// }
[modifiedIndexPaths sortUsingSelector:@selector(compare:)];
NSLog(@"Range update complete; modifiedIndexPaths: %@", [self descriptionWithIndexPaths:modifiedIndexPaths]);
NSLog(@"Range update complete; modifiedIndexPaths: %@, rangeMode: %d", [self descriptionWithIndexPaths:modifiedIndexPaths], rangeMode);
#endif
ASSignpostEnd(ASSignpostRangeControllerUpdate);

View File

@ -17,6 +17,7 @@
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
@class ASTraitCollection;
@ -27,14 +28,51 @@ NS_ASSUME_NONNULL_BEGIN
ASDISPLAYNODE_EXTERN_C_BEGIN
#pragma mark - ASPrimitiveContentSizeCategory
/**
* ASPrimitiveContentSizeCategory is a UIContentSizeCategory that can be used inside a struct.
*
* We need an unretained pointer because ARC can't manage struct memory.
*
* WARNING: DO NOT cast UIContentSizeCategory values to ASPrimitiveContentSizeCategory directly.
* Use ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory) instead.
* This is because we make some assumptions about the lifetime of the object it points to.
* Also note that cast from ASPrimitiveContentSizeCategory to UIContentSizeCategory is always safe.
*/
typedef __unsafe_unretained UIContentSizeCategory ASPrimitiveContentSizeCategory;
/**
* Safely casts from UIContentSizeCategory to ASPrimitiveContentSizeCategory.
*
* The UIKit documentation doesn't specify if we can receive a copy of the UIContentSizeCategory constant. While getting
* copies is fine with ARC, usage of unretained pointers requires us to ensure the lifetime of the object it points to.
* Manual retain&release of the UIContentSizeCategory object is not an option because it would require us to do that
* everywhere ASPrimitiveTraitCollection is used. This is error-prone and can lead to crashes and memory leaks. So, we
* explicitly limit possible values of ASPrimitiveContentSizeCategory to the predetermined set of global constants with
* known lifetime.
*
* @return a pointer to one of the UIContentSizeCategory constants.
*/
extern ASPrimitiveContentSizeCategory ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory sizeCategory);
#pragma mark - ASPrimitiveTraitCollection
typedef struct ASPrimitiveTraitCollection {
CGFloat displayScale;
UIUserInterfaceSizeClass horizontalSizeClass;
UIUserInterfaceIdiom userInterfaceIdiom;
UIUserInterfaceSizeClass verticalSizeClass;
CGFloat displayScale;
UIDisplayGamut displayGamut;
UIUserInterfaceIdiom userInterfaceIdiom;
UIForceTouchCapability forceTouchCapability;
UITraitEnvironmentLayoutDirection layoutDirection;
#if TARGET_OS_TV
UIUserInterfaceStyle userInterfaceStyle;
#endif
ASPrimitiveContentSizeCategory preferredContentSizeCategory;
CGSize containerSize;
} ASPrimitiveTraitCollection;
@ -124,11 +162,21 @@ ASDISPLAYNODE_EXTERN_C_END
AS_SUBCLASSING_RESTRICTED
@interface ASTraitCollection : NSObject
@property (nonatomic, assign, readonly) CGFloat displayScale;
@property (nonatomic, assign, readonly) UIUserInterfaceSizeClass horizontalSizeClass;
@property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom;
@property (nonatomic, assign, readonly) UIUserInterfaceSizeClass verticalSizeClass;
@property (nonatomic, assign, readonly) CGFloat displayScale;
@property (nonatomic, assign, readonly) UIDisplayGamut displayGamut;
@property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom;
@property (nonatomic, assign, readonly) UIForceTouchCapability forceTouchCapability;
@property (nonatomic, assign, readonly) UITraitEnvironmentLayoutDirection layoutDirection;
#if TARGET_OS_TV
@property (nonatomic, assign, readonly) UIUserInterfaceStyle userInterfaceStyle;
#endif
@property (nonatomic, assign, readonly) UIContentSizeCategory preferredContentSizeCategory;
@property (nonatomic, assign, readonly) CGSize containerSize;
+ (ASTraitCollection *)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits;
@ -136,18 +184,48 @@ AS_SUBCLASSING_RESTRICTED
+ (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection
containerSize:(CGSize)windowSize;
+ (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection
containerSize:(CGSize)windowSize
fallbackContentSizeCategory:(UIContentSizeCategory)fallbackContentSizeCategory;
+ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale
userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
containerSize:(CGSize)windowSize;
#if TARGET_OS_TV
+ (ASTraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
displayScale:(CGFloat)displayScale
displayGamut:(UIDisplayGamut)displayGamut
userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection
userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle
preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory
containerSize:(CGSize)windowSize;
#else
+ (ASTraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
displayScale:(CGFloat)displayScale
displayGamut:(UIDisplayGamut)displayGamut
userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection
preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory
containerSize:(CGSize)windowSize;
#endif
- (ASPrimitiveTraitCollection)primitiveTraitCollection;
- (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection;
@end
@interface ASTraitCollection (Deprecated)
+ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale
userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
containerSize:(CGSize)windowSize
ASDISPLAYNODE_DEPRECATED_MSG("Use full version of this method instead.");
@end
NS_ASSUME_NONNULL_END

View File

@ -20,6 +20,60 @@
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
#import <AsyncDisplayKit/ASLayoutElement.h>
#pragma mark - ASPrimitiveContentSizeCategory
// UIContentSizeCategoryUnspecified is available only in iOS 10.0 and later.
// This is used for compatibility with older iOS versions.
ASDISPLAYNODE_INLINE UIContentSizeCategory AS_UIContentSizeCategoryUnspecified() {
if (AS_AVAILABLE_IOS(10)) {
return UIContentSizeCategoryUnspecified;
} else {
return @"_UICTContentSizeCategoryUnspecified";
}
}
ASPrimitiveContentSizeCategory ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory sizeCategory) {
if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraSmall]) {
return UIContentSizeCategoryExtraSmall;
}
if ([sizeCategory isEqualToString:UIContentSizeCategorySmall]) {
return UIContentSizeCategorySmall;
}
if ([sizeCategory isEqualToString:UIContentSizeCategoryMedium]) {
return UIContentSizeCategoryMedium;
}
if ([sizeCategory isEqualToString:UIContentSizeCategoryLarge]) {
return UIContentSizeCategoryLarge;
}
if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraLarge]) {
return UIContentSizeCategoryExtraLarge;
}
if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
return UIContentSizeCategoryExtraExtraLarge;
}
if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
return UIContentSizeCategoryExtraExtraExtraLarge;
}
if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
return UIContentSizeCategoryAccessibilityMedium;
}
if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
return UIContentSizeCategoryAccessibilityLarge;
}
if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
return UIContentSizeCategoryAccessibilityExtraLarge;
}
if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
return UIContentSizeCategoryAccessibilityExtraExtraLarge;
}
if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
return UIContentSizeCategoryAccessibilityExtraExtraExtraLarge;
}
return AS_UIContentSizeCategoryUnspecified();
}
#pragma mark - ASPrimitiveTraitCollection
extern void ASTraitCollectionPropagateDown(id<ASLayoutElement> element, ASPrimitiveTraitCollection traitCollection) {
@ -32,63 +86,73 @@ extern void ASTraitCollectionPropagateDown(id<ASLayoutElement> element, ASPrimit
}
}
ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault()
{
ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault() {
return (ASPrimitiveTraitCollection) {
// Default values can be defined in here
.displayGamut = UIDisplayGamutUnspecified,
.userInterfaceIdiom = UIUserInterfaceIdiomUnspecified,
.layoutDirection = UITraitEnvironmentLayoutDirectionUnspecified,
.preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(AS_UIContentSizeCategoryUnspecified()),
.containerSize = CGSizeZero,
};
}
ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection)
{
ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) {
ASPrimitiveTraitCollection environmentTraitCollection = ASPrimitiveTraitCollectionMakeDefault();
environmentTraitCollection.displayScale = traitCollection.displayScale;
environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass;
environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass;
environmentTraitCollection.displayScale = traitCollection.displayScale;
environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom;
if (AS_AVAILABLE_IOS(9)) {
environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability;
environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability;
if (AS_AVAILABLE_IOS(10)) {
environmentTraitCollection.displayGamut = traitCollection.displayGamut;
environmentTraitCollection.layoutDirection = traitCollection.layoutDirection;
// preferredContentSizeCategory is also available on older iOS versions, but only via UIApplication class.
// It should be noted that [UIApplication sharedApplication] is unavailable because Texture is built with only extension-safe API.
environmentTraitCollection.preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(traitCollection.preferredContentSizeCategory);
#if TARGET_OS_TV
environmentTraitCollection.userInterfaceStyle = traitCollection.userInterfaceStyle;
#endif
}
return environmentTraitCollection;
}
BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs)
{
BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) {
UIContentSizeCategory leftSizeCategory = (UIContentSizeCategory)lhs.preferredContentSizeCategory;
UIContentSizeCategory rightSizeCategory = (UIContentSizeCategory)rhs.preferredContentSizeCategory;
return
lhs.verticalSizeClass == rhs.verticalSizeClass &&
lhs.horizontalSizeClass == rhs.horizontalSizeClass &&
lhs.displayScale == rhs.displayScale &&
lhs.displayGamut == rhs.displayGamut &&
lhs.userInterfaceIdiom == rhs.userInterfaceIdiom &&
lhs.forceTouchCapability == rhs.forceTouchCapability &&
lhs.layoutDirection == rhs.layoutDirection &&
#if TARGET_OS_TV
lhs.userInterfaceStyle == rhs.userInterfaceStyle &&
#endif
[leftSizeCategory isEqualToString:rightSizeCategory] && // Simple pointer comparison should be sufficient here
CGSizeEqualToSize(lhs.containerSize, rhs.containerSize);
}
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceIdiom(UIUserInterfaceIdiom idiom) {
if (AS_AVAILABLE_IOS(9)) {
switch (idiom) {
case UIUserInterfaceIdiomTV:
return @"TV";
case UIUserInterfaceIdiomPad:
return @"Pad";
case UIUserInterfaceIdiomPhone:
return @"Phone";
case UIUserInterfaceIdiomCarPlay:
return @"CarPlay";
default:
return @"Unspecified";
}
} else {
switch (idiom) {
case UIUserInterfaceIdiomPad:
return @"Pad";
case UIUserInterfaceIdiomPhone:
return @"Phone";
default:
return @"Unspecified";
}
switch (idiom) {
case UIUserInterfaceIdiomTV:
return @"TV";
case UIUserInterfaceIdiomPad:
return @"Pad";
case UIUserInterfaceIdiomPhone:
return @"Phone";
case UIUserInterfaceIdiomCarPlay:
return @"CarPlay";
default:
return @"Unspecified";
}
}
@ -116,14 +180,58 @@ ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceSizeClass(UIUserInt
}
}
NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits)
{
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIDisplayGamut(UIDisplayGamut displayGamut) {
switch (displayGamut) {
case UIDisplayGamutSRGB:
return @"sRGB";
case UIDisplayGamutP3:
return @"P3";
default:
return @"Unspecified";
}
}
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUITraitEnvironmentLayoutDirection(UITraitEnvironmentLayoutDirection layoutDirection) {
switch (layoutDirection) {
case UITraitEnvironmentLayoutDirectionLeftToRight:
return @"LeftToRight";
case UITraitEnvironmentLayoutDirectionRightToLeft:
return @"RightToLeft";
default:
return @"Unspecified";
}
}
#if TARGET_OS_TV
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceStyle(UIUserInterfaceStyle userInterfaceStyle) {
switch (userInterfaceStyle) {
case UIUserInterfaceStyleLight:
return @"Light";
case UIUserInterfaceStyleDark:
return @"Dark";
default:
return @"Unspecified";
}
}
#endif
NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits) {
NSMutableArray<NSDictionary *> *props = [NSMutableArray array];
[props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }];
[props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }];
[props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }];
[props addObject:@{ @"verticalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.verticalSizeClass) }];
[props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }];
[props addObject:@{ @"displayScale": [NSString stringWithFormat: @"%.0lf", (double)traits.displayScale] }];
[props addObject:@{ @"displayGamut": AS_NSStringFromUIDisplayGamut(traits.displayGamut) }];
[props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }];
[props addObject:@{ @"forceTouchCapability": AS_NSStringFromUIForceTouchCapability(traits.forceTouchCapability) }];
[props addObject:@{ @"layoutDirection": AS_NSStringFromUITraitEnvironmentLayoutDirection(traits.layoutDirection) }];
#if TARGET_OS_TV
[props addObject:@{ @"userInterfaceStyle": AS_NSStringFromUIUserInterfaceStyle(traits.userInterfaceStyle) }];
#endif
[props addObject:@{ @"preferredContentSizeCategory": (UIContentSizeCategory)traits.preferredContentSizeCategory }];
[props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }];
return ASObjectDescriptionMakeWithoutObject(props);
}
@ -131,73 +239,238 @@ NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection trai
@implementation ASTraitCollection
- (instancetype)initWithDisplayScale:(CGFloat)displayScale
userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
containerSize:(CGSize)windowSize
#if TARGET_OS_TV
- (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
displayScale:(CGFloat)displayScale
displayGamut:(UIDisplayGamut)displayGamut
userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection
userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle
preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory
containerSize:(CGSize)windowSize
{
self = [super init];
if (self) {
_displayScale = displayScale;
_userInterfaceIdiom = userInterfaceIdiom;
_horizontalSizeClass = horizontalSizeClass;
_verticalSizeClass = verticalSizeClass;
_displayScale = displayScale;
_displayGamut = displayGamut;
_userInterfaceIdiom = userInterfaceIdiom;
_forceTouchCapability = forceTouchCapability;
_layoutDirection = layoutDirection;
_userInterfaceStyle = userInterfaceStyle;
_preferredContentSizeCategory = preferredContentSizeCategory;
_containerSize = windowSize;
}
return self;
}
+ (instancetype)traitCollectionWithDisplayScale:(CGFloat)displayScale
userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
containerSize:(CGSize)windowSize
+ (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
displayScale:(CGFloat)displayScale
displayGamut:(UIDisplayGamut)displayGamut
userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection
userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle
preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory
containerSize:(CGSize)windowSize
{
return [[self alloc] initWithDisplayScale:displayScale
userInterfaceIdiom:userInterfaceIdiom
horizontalSizeClass:horizontalSizeClass
verticalSizeClass:verticalSizeClass
forceTouchCapability:forceTouchCapability
containerSize:windowSize];
return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass
verticalSizeClass:verticalSizeClass
displayScale:displayScale
displayGamut:displayGamut
userInterfaceIdiom:userInterfaceIdiom
forceTouchCapability:forceTouchCapability
layoutDirection:layoutDirection
userInterfaceStyle:userIntefaceStyle
preferredContentSizeCategory:preferredContentSizeCategory
containerSize:windowSize];
}
#else
- (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
displayScale:(CGFloat)displayScale
displayGamut:(UIDisplayGamut)displayGamut
userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection
preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory
containerSize:(CGSize)windowSize
{
self = [super init];
if (self) {
_horizontalSizeClass = horizontalSizeClass;
_verticalSizeClass = verticalSizeClass;
_displayScale = displayScale;
_displayGamut = displayGamut;
_userInterfaceIdiom = userInterfaceIdiom;
_forceTouchCapability = forceTouchCapability;
_layoutDirection = layoutDirection;
_preferredContentSizeCategory = preferredContentSizeCategory;
_containerSize = windowSize;
}
return self;
}
+ (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
displayScale:(CGFloat)displayScale
displayGamut:(UIDisplayGamut)displayGamut
userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection
preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory
containerSize:(CGSize)windowSize
{
return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass
verticalSizeClass:verticalSizeClass
displayScale:displayScale
displayGamut:displayGamut
userInterfaceIdiom:userInterfaceIdiom
forceTouchCapability:forceTouchCapability
layoutDirection:layoutDirection
preferredContentSizeCategory:preferredContentSizeCategory
containerSize:windowSize];
}
#endif
+ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale
userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
containerSize:(CGSize)windowSize
{
#if TARGET_OS_TV
return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass
verticalSizeClass:verticalSizeClass
displayScale:displayScale
displayGamut:UIDisplayGamutUnspecified
userInterfaceIdiom:userInterfaceIdiom
forceTouchCapability:forceTouchCapability
layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified
userInterfaceStyle:UIUserInterfaceStyleUnspecified
preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified()
containerSize:windowSize];
#else
return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass
verticalSizeClass:verticalSizeClass
displayScale:displayScale
displayGamut:UIDisplayGamutUnspecified
userInterfaceIdiom:userInterfaceIdiom
forceTouchCapability:forceTouchCapability
layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified
preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified()
containerSize:windowSize];
#endif
}
+ (instancetype)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits
{
return [self traitCollectionWithDisplayScale:traits.displayScale
userInterfaceIdiom:traits.userInterfaceIdiom
horizontalSizeClass:traits.horizontalSizeClass
verticalSizeClass:traits.verticalSizeClass
forceTouchCapability:traits.forceTouchCapability
containerSize:traits.containerSize];
#if TARGET_OS_TV
return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass
verticalSizeClass:traits.verticalSizeClass
displayScale:traits.displayScale
displayGamut:traits.displayGamut
userInterfaceIdiom:traits.userInterfaceIdiom
forceTouchCapability:traits.forceTouchCapability
layoutDirection:traits.layoutDirection
userInterfaceStyle:traits.userInterfaceStyle
preferredContentSizeCategory:(UIContentSizeCategory)traits.preferredContentSizeCategory
containerSize:traits.containerSize];
#else
return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass
verticalSizeClass:traits.verticalSizeClass
displayScale:traits.displayScale
displayGamut:traits.displayGamut
userInterfaceIdiom:traits.userInterfaceIdiom
forceTouchCapability:traits.forceTouchCapability
layoutDirection:traits.layoutDirection
preferredContentSizeCategory:(UIContentSizeCategory)traits.preferredContentSizeCategory
containerSize:traits.containerSize];
#endif
}
+ (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection
containerSize:(CGSize)windowSize
containerSize:(CGSize)windowSize
{
UIForceTouchCapability forceTouch = UIForceTouchCapabilityUnknown;
if(AS_AVAILABLE_IOS(9)) {
forceTouch = traitCollection.forceTouchCapability;
return [self traitCollectionWithUITraitCollection:traitCollection
containerSize:windowSize
fallbackContentSizeCategory:AS_UIContentSizeCategoryUnspecified()];
}
+ (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection
containerSize:(CGSize)windowSize
fallbackContentSizeCategory:(UIContentSizeCategory)fallbackContentSizeCategory
{
UIDisplayGamut displayGamut;
UITraitEnvironmentLayoutDirection layoutDirection;
UIContentSizeCategory sizeCategory;
#if TARGET_OS_TV
UIUserInterfaceStyle userInterfaceStyle;
#endif
if (AS_AVAILABLE_IOS(10)) {
displayGamut = traitCollection.displayGamut;
layoutDirection = traitCollection.layoutDirection;
sizeCategory = traitCollection.preferredContentSizeCategory;
#if TARGET_OS_TV
userInterfaceStyle = traitCollection.userInterfaceStyle;
#endif
} else {
displayGamut = UIDisplayGamutUnspecified;
layoutDirection = UITraitEnvironmentLayoutDirectionUnspecified;
sizeCategory = fallbackContentSizeCategory;
#if TARGET_OS_TV
userInterfaceStyle = UIUserInterfaceStyleUnspecified;
#endif
}
return [self traitCollectionWithDisplayScale:traitCollection.displayScale
userInterfaceIdiom:traitCollection.userInterfaceIdiom
horizontalSizeClass:traitCollection.horizontalSizeClass
verticalSizeClass:traitCollection.verticalSizeClass
forceTouchCapability:forceTouch
containerSize:windowSize];
#if TARGET_OS_TV
return [self traitCollectionWithHorizontalSizeClass:traitCollection.horizontalSizeClass
verticalSizeClass:traitCollection.verticalSizeClass
displayScale:traitCollection.displayScale
displayGamut:displayGamut
userInterfaceIdiom:traitCollection.userInterfaceIdiom
forceTouchCapability:traitCollection.forceTouchCapability
layoutDirection:layoutDirection
userInterfaceStyle:userInterfaceStyle
preferredContentSizeCategory:sizeCategory
containerSize:windowSize];
#else
return [self traitCollectionWithHorizontalSizeClass:traitCollection.horizontalSizeClass
verticalSizeClass:traitCollection.verticalSizeClass
displayScale:traitCollection.displayScale
displayGamut:displayGamut
userInterfaceIdiom:traitCollection.userInterfaceIdiom
forceTouchCapability:traitCollection.forceTouchCapability
layoutDirection:layoutDirection
preferredContentSizeCategory:sizeCategory
containerSize:windowSize];
#endif
}
- (ASPrimitiveTraitCollection)primitiveTraitCollection
{
return (ASPrimitiveTraitCollection) {
.displayScale = self.displayScale,
.horizontalSizeClass = self.horizontalSizeClass,
.userInterfaceIdiom = self.userInterfaceIdiom,
.verticalSizeClass = self.verticalSizeClass,
.displayScale = self.displayScale,
.displayGamut = self.displayGamut,
.userInterfaceIdiom = self.userInterfaceIdiom,
.forceTouchCapability = self.forceTouchCapability,
.layoutDirection = self.layoutDirection,
#if TARGET_OS_TV
.userInterfaceStyle = self.userInterfaceStyle,
#endif
.preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(self.preferredContentSizeCategory),
.containerSize = self.containerSize,
};
}
@ -208,12 +481,19 @@ NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection trai
return YES;
}
return self.displayScale == traitCollection.displayScale &&
self.horizontalSizeClass == traitCollection.horizontalSizeClass &&
self.verticalSizeClass == traitCollection.verticalSizeClass &&
self.userInterfaceIdiom == traitCollection.userInterfaceIdiom &&
CGSizeEqualToSize(self.containerSize, traitCollection.containerSize) &&
self.forceTouchCapability == traitCollection.forceTouchCapability;
return
self.horizontalSizeClass == traitCollection.horizontalSizeClass &&
self.verticalSizeClass == traitCollection.verticalSizeClass &&
self.displayScale == traitCollection.displayScale &&
self.displayGamut == traitCollection.displayGamut &&
self.userInterfaceIdiom == traitCollection.userInterfaceIdiom &&
self.forceTouchCapability == traitCollection.forceTouchCapability &&
self.layoutDirection == traitCollection.layoutDirection &&
#if TARGET_OS_TV
self.userInterfaceStyle == traitCollection.userInterfaceStyle &&
#endif
[self.preferredContentSizeCategory isEqualToString:traitCollection.preferredContentSizeCategory] &&
CGSizeEqualToSize(self.containerSize, traitCollection.containerSize);
}
@end

View File

@ -19,6 +19,8 @@
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASBlockTypes.h>
NS_ASSUME_NONNULL_BEGIN
@class ASDisplayNode;
@protocol _ASDisplayLayerDelegate;
@ -28,7 +30,7 @@
@discussion This property overrides the CALayer category method which implements this via associated objects.
This should result in much better performance for _ASDisplayLayers.
*/
@property (nonatomic, weak) ASDisplayNode *asyncdisplaykit_node;
@property (nullable, nonatomic, weak) ASDisplayNode *asyncdisplaykit_node;
/**
@summary Set to YES to enable asynchronous display for the receiver.
@ -57,7 +59,7 @@
@desc The asyncDelegate will have the opportunity to override the methods related to async display.
*/
@property (atomic, weak) id<_ASDisplayLayerDelegate> asyncDelegate;
@property (nullable, atomic, weak) id<_ASDisplayLayerDelegate> asyncDelegate;
/**
@summary Suspends both asynchronous and synchronous display of the receiver if YES.
@ -109,7 +111,10 @@
@param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return.
@param isRasterizing YES if the layer is being rasterized into another layer, in which case drawRect: probably wants to avoid doing things like filling its bounds with a zero-alpha color to clear the backing store.
*/
+ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing;
+ (void)drawRect:(CGRect)bounds
withParameters:(nullable id)parameters
isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock
isRasterizing:(BOOL)isRasterizing;
/**
@summary Delegate override to provide new layer contents as a UIImage.
@ -117,7 +122,8 @@
@param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return.
@return A UIImage with contents that are ready to display on the main thread. Make sure that the image is already decoded before returning it here.
*/
+ (UIImage *)displayWithParameters:(id<NSObject>)parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock;
+ (UIImage *)displayWithParameters:(nullable id<NSObject>)parameters
isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock;
// Called on the main thread only
@ -147,3 +153,5 @@
- (void)cancelDisplayAsyncLayer:(_ASDisplayLayer *)asyncLayer;
@end
NS_ASSUME_NONNULL_END

View File

@ -17,11 +17,21 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
// This class is only for use by ASDisplayNode and should never be subclassed or used directly.
// Note that the "node" property is added to UIView directly via a category in ASDisplayNode.
@class ASDisplayNode;
@interface _ASDisplayView : UIView
/**
@discussion This property overrides the UIView category method which implements this via associated objects.
This should result in much better performance for _ASDisplayView.
*/
@property (nullable, nonatomic, weak) ASDisplayNode *asyncdisplaykit_node;
// These methods expose a way for ASDisplayNode touch events to let the view call super touch events
// Some UIKit mechanisms, like UITableView and UICollectionView selection handling, require this to work
- (void)__forwardTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
@ -30,3 +40,5 @@
- (void)__forwardTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
@end
NS_ASSUME_NONNULL_END

View File

@ -27,7 +27,6 @@
#import <AsyncDisplayKit/ASLayout.h>
@interface _ASDisplayView ()
@property (nullable, atomic, weak, readwrite) ASDisplayNode *asyncdisplaykit_node;
// Keep the node alive while its view is active. If you create a view, add its layer to a layer hierarchy, then release
// the view, the layer retains the view to prevent a crash. This replicates this behaviour for the node abstraction.

View File

@ -192,7 +192,7 @@ extern NSString * const ASLayoutElementStyleLayoutPositionProperty;
#pragma mark - Sizing
/**
* @abstract The width property specifies the height of the content area of an ASLayoutElement.
* @abstract The width property specifies the width of the content area of an ASLayoutElement.
* The minWidth and maxWidth properties override width.
* Defaults to ASDimensionAuto
*/

View File

@ -160,6 +160,21 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh
}
}
/**
* NOTE: It is suggested practice on the Web to override invalidationContextForInteractivelyMovingItems… and call out to the
* data source to move the item (so that if e.g. the item size depends on the data, you get the data you expect). However, as of iOS 11 this
* doesn't work, because UICV machinery will also call out to the data source to move the item after the interaction is done. The result is
* that your data source state will be incorrect due to this last move call. Plus it's just an API violation.
*
* Things tried:
* - Doing the speculative data source moves, and then UNDOING the last one in invalidationContextForEndingInteractiveMovementOfItems…
* but this does not work because the UICV machinery informs its data source before it calls that method on us, so we are too late.
*
* The correct practice is to use the UIDataSourceTranslating API introduced in iOS 11. Currently Texture does not support this API but we can
* build it if there is demand. We could add an id<UIDataSourceTranslating> field onto the layout context object, and the layout client can
* use data source index paths when it reads nodes or other data source data.
*/
- (CGSize)collectionViewContentSize
{
ASDisplayNodeAssertMainThread();

View File

@ -21,168 +21,12 @@
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASSignpost.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
@interface ASDrawingContext : NSObject
@property (nonatomic, readonly) CGFloat scale;
@property (nonatomic, readonly) int32_t bytesPerRow;
@property (nonatomic, readonly) void *bytes;
- (instancetype)initWithSize:(CGSize)size scale:(CGFloat)scale opaque:(bool)opaque;
- (UIImage *)generateImage;
- (void)withContext:(void (^)(CGContextRef))f;
- (void)withFlippedContext:(void (^)(CGContextRef))f;
- (void)pushCurrent;
- (void)popCurrent;
@end
static void DrawingContextDataProviderReleaseDataCallback(void *info, __unused const void *data, __unused size_t size) {
free(info);
}
@interface ASDrawingContext () {
CGSize _size;
CGSize _scaledSize;
CGBitmapInfo _bitmapInfo;
int32_t _length;
CGDataProviderRef _provider;
CGContextRef _context;
bool _didPush;
}
@end
@implementation ASDrawingContext
- (instancetype)initWithSize:(CGSize)size scale:(CGFloat)scale opaque:(bool)opaque {
self = [super init];
if (self != nil) {
ASDisplayNodeAssert(scale > 0.0f, @"scale > 0.0f");
_size = size;
_scale = scale;
_scaledSize = CGSizeMake(size.width * _scale, size.height * _scale);
_bytesPerRow = (4 * ((int32_t)(_scaledSize.width)) + 15) & (~15);
_length = _bytesPerRow * ((int32_t)(_scaledSize.height));
_bitmapInfo = kCGBitmapByteOrder32Little | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst);
_bytes = malloc(_length);
memset(_bytes, 0, _length);
_provider = CGDataProviderCreateWithData(_bytes, _bytes, _length, &DrawingContextDataProviderReleaseDataCallback);
}
return self;
}
- (void)dealloc {
if (_context != nil) {
CGContextRelease(_context);
}
if (_provider != nil) {
CGDataProviderRelease(_provider);
}
}
- (void)withContext:(void (^)(CGContextRef))f {
if (_context == nil) {
static CGColorSpaceRef deviceColorSpace = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
deviceColorSpace = CGColorSpaceCreateDeviceRGB();
});
_context = CGBitmapContextCreate(_bytes, (int32_t)_scaledSize.width, (int32_t)_scaledSize.height, 8, _bytesPerRow, deviceColorSpace, _bitmapInfo);
if (_context != nil) {
CGContextScaleCTM(_context, _scale, _scale);
}
}
if (_context != nil) {
CGContextTranslateCTM(_context, _size.width / 2.0f, _size.height / 2.0f);
CGContextScaleCTM(_context, 1.0f, -1.0f);
CGContextTranslateCTM(_context, -_size.width / 2.0f, -_size.height / 2.0f);
if (f) {
f(_context);
}
CGContextTranslateCTM(_context, _size.width / 2.0f, _size.height / 2.0f);
CGContextScaleCTM(_context, 1.0f, -1.0f);
CGContextTranslateCTM(_context, -_size.width / 2.0f, -_size.height / 2.0f);
}
}
- (void)withFlippedContext:(void (^)(CGContextRef))f {
if (_context == nil) {
static CGColorSpaceRef deviceColorSpace = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
deviceColorSpace = CGColorSpaceCreateDeviceRGB();
});
_context = CGBitmapContextCreate(_bytes, (int32_t)_scaledSize.width, (int32_t)_scaledSize.height, 8, _bytesPerRow, deviceColorSpace, _bitmapInfo);
if (_context != nil) {
CGContextScaleCTM(_context, _scale, _scale);
}
}
if (_context != nil) {
if (f) {
f(_context);
}
}
}
- (void)pushCurrent {
if (_context == nil) {
static CGColorSpaceRef deviceColorSpace = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
deviceColorSpace = CGColorSpaceCreateDeviceRGB();
});
_context = CGBitmapContextCreate(_bytes, (int32_t)_scaledSize.width, (int32_t)_scaledSize.height, 8, _bytesPerRow, deviceColorSpace, _bitmapInfo);
if (_context != nil) {
CGContextScaleCTM(_context, _scale, _scale);
}
}
if (_context != nil) {
CGContextTranslateCTM(_context, _size.width / 2.0f, _size.height / 2.0f);
CGContextScaleCTM(_context, 1.0f, -1.0f);
CGContextTranslateCTM(_context, -_size.width / 2.0f, -_size.height / 2.0f);
UIGraphicsPushContext(_context);
_didPush = true;
}
}
- (void)popCurrent {
if (_context != nil && _didPush) {
_didPush = false;
UIGraphicsPopContext();
}
}
- (UIImage *)generateImage {
static CGColorSpaceRef deviceColorSpace = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
deviceColorSpace = CGColorSpaceCreateDeviceRGB();
});
CGImageRef image = CGImageCreate((int32_t)_scaledSize.width, (int32_t)_scaledSize.height, 8, 32, _bytesPerRow, deviceColorSpace, _bitmapInfo, _provider, nil, false, kCGRenderingIntentDefault);
if (image != nil) {
UIImage *uiImage = [[UIImage alloc] initWithCGImage:image scale:_scale orientation:UIImageOrientationUp];
CGImageRelease(image);
return uiImage;
} else {
return nil;
}
}
@end
@interface ASDisplayNode () <_ASDisplayLayerDelegate>
@end
@ -375,25 +219,14 @@ static void DrawingContextDataProviderReleaseDataCallback(void *info, __unused c
displayBlock = ^id{
CHECK_CANCELLED_AND_RETURN_NIL();
ASDrawingContext *context = [[ASDrawingContext alloc] initWithSize:bounds.size scale:contentsScaleForDisplay opaque:opaque];
[context pushCurrent];
for (dispatch_block_t block in displayBlocks) {
CHECK_CANCELLED_AND_RETURN_NIL([context popCurrent]);
block();
}
[context popCurrent];
UIImage *image = [context generateImage];
/*UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
for (dispatch_block_t block in displayBlocks) {
CHECK_CANCELLED_AND_RETURN_NIL(UIGraphicsEndImageContext());
CHECK_CANCELLED_AND_RETURN_NIL(ASGraphicsEndImageContext());
block();
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();*/
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
ASDN_DELAY_FOR_DISPLAY();
return image;
@ -402,13 +235,9 @@ static void DrawingContextDataProviderReleaseDataCallback(void *info, __unused c
displayBlock = ^id{
CHECK_CANCELLED_AND_RETURN_NIL();
ASDrawingContext *context = nil;
if (shouldCreateGraphicsContext) {
//UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
//CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); );
context = [[ASDrawingContext alloc] initWithSize:bounds.size scale:contentsScaleForDisplay opaque:opaque];
[context pushCurrent];
CHECK_CANCELLED_AND_RETURN_NIL( [context popCurrent]; );
ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); );
}
CGContextRef currentContext = UIGraphicsGetCurrentContext();
@ -427,13 +256,8 @@ static void DrawingContextDataProviderReleaseDataCallback(void *info, __unused c
[self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor];
if (shouldCreateGraphicsContext) {
CHECK_CANCELLED_AND_RETURN_NIL( [context popCurrent]; );
[context popCurrent];
image = [context generateImage];
context = nil;
//CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); );
//image = UIGraphicsGetImageFromCurrentImageContext();
//UIGraphicsEndImageContext();
CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); );
image = ASGraphicsGetImageAndEndCurrentContext();
}
ASDN_DELAY_FOR_DISPLAY();
@ -507,7 +331,7 @@ static void DrawingContextDataProviderReleaseDataCallback(void *info, __unused c
bounds.size.height *= contentsScale;
CGFloat white = 0.0f, alpha = 0.0f;
[backgroundColor getWhite:&white alpha:&alpha];
UIGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale);
ASGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale);
[*image drawInRect:bounds];
} else {
bounds = CGContextGetClipBoundingBox(context);
@ -537,8 +361,7 @@ static void DrawingContextDataProviderReleaseDataCallback(void *info, __unused c
[roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil.
if (*image) {
*image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
*image = ASGraphicsGetImageAndEndCurrentContext();
}
}
}

View File

@ -141,6 +141,10 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyStateChange(ASHierarc
// delegate to inform of ASInterfaceState changes (used by ASNodeController)
@property (nonatomic, weak) id<ASInterfaceStateDelegate> interfaceStateDelegate;
// The -pendingInterfaceState holds the value that will be applied to -interfaceState by the
// ASCATransactionQueue. If already applied, it matches -interfaceState. Thread-safe access.
@property (nonatomic, readonly) ASInterfaceState pendingInterfaceState;
// These methods are recursive, and either union or remove the provided interfaceState to all sub-elements.
- (void)enterInterfaceState:(ASInterfaceState)interfaceState;
- (void)exitInterfaceState:(ASInterfaceState)interfaceState;

View File

@ -186,17 +186,12 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo
- (CGFloat)cornerRadius
{
ASDN::MutexLocker l(__instanceLock__);
if (_cornerRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) {
return self.layerCornerRadius;
} else {
return _cornerRadius;
}
return _cornerRadius;
}
- (void)setCornerRadius:(CGFloat)newCornerRadius
{
ASDN::MutexLocker l(__instanceLock__);
[self updateCornerRoundingWithType:_cornerRoundingType cornerRadius:newCornerRadius];
[self updateCornerRoundingWithType:self.cornerRoundingType cornerRadius:newCornerRadius];
}
- (ASCornerRoundingType)cornerRoundingType
@ -207,8 +202,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo
- (void)setCornerRoundingType:(ASCornerRoundingType)newRoundingType
{
ASDN::MutexLocker l(__instanceLock__);
[self updateCornerRoundingWithType:newRoundingType cornerRadius:_cornerRadius];
[self updateCornerRoundingWithType:newRoundingType cornerRadius:self.cornerRadius];
}
- (NSString *)contentsGravity
@ -857,21 +851,16 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo
- (UISemanticContentAttribute)semanticContentAttribute
{
_bridge_prologue_read;
if (AS_AT_LEAST_IOS9) {
return _getFromViewOnly(semanticContentAttribute);
}
return UISemanticContentAttributeUnspecified;
return _getFromViewOnly(semanticContentAttribute);
}
- (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentAttribute
{
_bridge_prologue_write;
if (AS_AT_LEAST_IOS9) {
_setToViewOnly(semanticContentAttribute, semanticContentAttribute);
_setToViewOnly(semanticContentAttribute, semanticContentAttribute);
#if YOGA
[self semanticContentAttributeDidChange:semanticContentAttribute];
[self semanticContentAttributeDidChange:semanticContentAttribute];
#endif
}
}
@end

View File

@ -77,7 +77,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
{
@package
_ASPendingState *_pendingViewState;
ASInterfaceState _pendingInterfaceState;
UIView *_view;
CALayer *_layer;

View File

@ -102,6 +102,14 @@ typedef struct {
[self.delegate scrollViewWillBeginDragging:scrollView];
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
// IGListAdapter doesn't implement scrollViewWillEndDragging yet (pending pull request), so we need this check for now. Doesn't hurt to have it anyways :)
if ([self.delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
[self.delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
[self.delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];

View File

@ -143,8 +143,9 @@ CGFloat ASScreenScale()
static CGFloat __scale = 0.0;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ASDisplayNodeCAssertMainThread();
__scale = [[UIScreen mainScreen] scale];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0);
__scale = CGContextGetCTM(UIGraphicsGetCurrentContext()).a;
UIGraphicsEndImageContext();
});
return __scale;
}

View File

@ -0,0 +1,22 @@
//
// ASNetworkImageLoadInfo+Private.h
// AsyncDisplayKit
//
// Created by Adlai on 1/30/18.
// Copyright © 2018 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/ASNetworkImageLoadInfo.h>
NS_ASSUME_NONNULL_BEGIN
@interface ASNetworkImageLoadInfo ()
- (instancetype)initWithURL:(NSURL *)url
sourceType:(ASNetworkImageSourceType)sourceType
downloadIdentifier:(nullable id)downloadIdentifier
userInfo:(nullable id)userInfo;
@end
NS_ASSUME_NONNULL_END

View File

@ -405,25 +405,9 @@ dispatch_semaphore_signal(_lock);
container->_readonly = YES;
maximumNumberOfRows = container.maximumNumberOfRows;
// CoreText bug when draw joined emoji since iOS 8.3.
// See -[NSMutableAttributedString setClearColorToJoinedEmoji] for more information.
static BOOL needFixJoinedEmojiBug = NO;
// It may use larger constraint size when create CTFrame with
// CTFramesetterCreateFrame in iOS 10.
static BOOL needFixLayoutSizeBug = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
double systemVersionDouble = [UIDevice currentDevice].systemVersion.doubleValue;
if (8.3 <= systemVersionDouble && systemVersionDouble < 9) {
needFixJoinedEmojiBug = YES;
}
if (systemVersionDouble >= 10) {
needFixLayoutSizeBug = YES;
}
});
if (needFixJoinedEmojiBug) {
[((NSMutableAttributedString *)text) as_setClearColorToJoinedEmoji];
}
BOOL needFixLayoutSizeBug = AS_AT_LEAST_IOS10;
layout = [[ASTextLayout alloc] _init];
layout.text = text;

View File

@ -63,7 +63,10 @@ ASTextAttributeType ASTextAttributeGetType(NSString *name){
dic[(id)kCTSuperscriptAttributeName] = UIKit; //it's a CoreText attrubite, but only supported by UIKit...
dic[NSVerticalGlyphFormAttributeName] = All;
dic[(id)kCTGlyphInfoAttributeName] = CoreText_ASText;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
dic[(id)kCTCharacterShapeAttributeName] = CoreText_ASText;
#pragma clang diagnostic pop
dic[(id)kCTRunDelegateAttributeName] = CoreText_ASText;
dic[(id)kCTBaselineClassAttributeName] = CoreText_ASText;
dic[(id)kCTBaselineInfoAttributeName] = CoreText_ASText;

View File

@ -1357,21 +1357,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)as_appendString:(NSString *)string;
/**
Set foreground color with [UIColor clearColor] in joined-emoji range.
Emoji drawing will not be affected by the foreground color.
@discussion In iOS 8.3, Apple releases some new diversified emojis.
There's some single emoji which can be assembled to a new 'joined-emoji'.
The joiner is unicode character 'ZERO WIDTH JOINER' (U+200D).
For example: 👨👩👧👧 -> 👨👩👧👧.
When there are more than 5 'joined-emoji' in a same CTLine, CoreText may render some
extra glyphs above the emoji. It's a bug in CoreText, try this method to avoid.
This bug is fixed in iOS 9.
*/
- (void)as_setClearColorToJoinedEmoji;
/**
Removes all discontinuous attributes in a specified range.
See `allDiscontinuousAttributeKeys`.

View File

@ -600,7 +600,10 @@ return style. _attr_;
dispatch_once(&onceToken, ^{
failSet = [NSMutableSet new];
[failSet addObject:(id)kCTGlyphInfoAttributeName];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[failSet addObject:(id)kCTCharacterShapeAttributeName];
#pragma clang diagnostic pop
[failSet addObject:(id)kCTLanguageAttributeName];
[failSet addObject:(id)kCTRunDelegateAttributeName];
[failSet addObject:(id)kCTBaselineClassAttributeName];
@ -1049,7 +1052,10 @@ style. _attr_ = _attr_; \
}
- (void)as_setCharacterShape:(NSNumber *)characterShape range:(NSRange)range {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[self as_setAttribute:(id)kCTCharacterShapeAttributeName value:characterShape range:range];
#pragma clang diagnostic pop
}
- (void)as_setRunDelegate:(CTRunDelegateRef)runDelegate range:(NSRange)range {
@ -1178,51 +1184,6 @@ style. _attr_ = _attr_; \
[self as_removeDiscontinuousAttributesInRange:NSMakeRange(length, string.length)];
}
- (void)as_setClearColorToJoinedEmoji {
NSString *str = self.string;
if (str.length < 8) return;
// Most string do not contains the joined-emoji, test the joiner first.
BOOL containsJoiner = NO;
{
CFStringRef cfStr = (__bridge CFStringRef)str;
BOOL needFree = NO;
UniChar *chars = NULL;
chars = (UniChar *)CFStringGetCharactersPtr(cfStr);
if (!chars) {
chars = (UniChar *)malloc(str.length * sizeof(UniChar));
if (chars) {
needFree = YES;
CFStringGetCharacters(cfStr, CFRangeMake(0, str.length), chars);
}
}
if (!chars) { // fail to get unichar..
containsJoiner = YES;
} else {
for (int i = 0, max = (int)str.length; i < max; i++) {
if (chars[i] == 0x200D) { // 'ZERO WIDTH JOINER' (U+200D)
containsJoiner = YES;
break;
}
}
if (needFree) free(chars);
}
}
if (!containsJoiner) return;
// NSRegularExpression is designed to be immutable and thread safe.
static NSRegularExpression *regex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
regex = [NSRegularExpression regularExpressionWithPattern:@"((👨‍👩‍👧‍👦|👨‍👩‍👦‍👦|👨‍👩‍👧‍👧|👩‍👩‍👧‍👦|👩‍👩‍👦‍👦|👩‍👩‍👧‍👧|👨‍👨‍👧‍👦|👨‍👨‍👦‍👦|👨‍👨‍👧‍👧)+|(👨‍👩‍👧|👩‍👩‍👦|👩‍👩‍👧|👨‍👨‍👦|👨‍👨‍👧))" options:kNilOptions error:nil];
});
UIColor *clear = [UIColor clearColor];
[regex enumerateMatchesInString:str options:kNilOptions range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[self as_setColor:clear range:result.range];
}];
}
- (void)as_removeDiscontinuousAttributesInRange:(NSRange)range {
NSArray *keys = [NSMutableAttributedString as_allDiscontinuousAttributeKeys];
for (NSString *key in keys) {

View File

@ -295,9 +295,7 @@ static BOOL defaultAllowsEdgeAntialiasing = NO;
accessibilityActivationPoint = CGPointZero;
accessibilityPath = nil;
edgeAntialiasingMask = (kCALayerLeftEdge | kCALayerRightEdge | kCALayerTopEdge | kCALayerBottomEdge);
if (AS_AVAILABLE_IOS(9)) {
semanticContentAttribute = UISemanticContentAttributeUnspecified;
}
semanticContentAttribute = UISemanticContentAttributeUnspecified;
return self;
}
@ -1051,10 +1049,8 @@ static BOOL defaultAllowsEdgeAntialiasing = NO;
if (flags.setOpaque)
ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired");
if (AS_AVAILABLE_IOS(9)) {
if (flags.setSemanticContentAttribute) {
view.semanticContentAttribute = semanticContentAttribute;
}
if (flags.setSemanticContentAttribute) {
view.semanticContentAttribute = semanticContentAttribute;
}
if (flags.setIsAccessibilityElement)
@ -1215,9 +1211,7 @@ static BOOL defaultAllowsEdgeAntialiasing = NO;
pendingState.allowsGroupOpacity = layer.allowsGroupOpacity;
pendingState.allowsEdgeAntialiasing = layer.allowsEdgeAntialiasing;
pendingState.edgeAntialiasingMask = layer.edgeAntialiasingMask;
if (AS_AVAILABLE_IOS(9)) {
pendingState.semanticContentAttribute = view.semanticContentAttribute;
}
pendingState.semanticContentAttribute = view.semanticContentAttribute;
pendingState.isAccessibilityElement = view.isAccessibilityElement;
pendingState.accessibilityLabel = view.accessibilityLabel;
pendingState.accessibilityHint = view.accessibilityHint;

View File

@ -16,6 +16,7 @@
//
#import <AsyncDisplayKit/UIImage+ASConvenience.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASAssert.h>
@ -138,7 +139,7 @@ UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollectio
// We should probably check if the background color has any alpha component but that
// might be expensive due to needing to check mulitple color spaces.
UIGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale);
ASGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale);
BOOL contextIsClean = YES;
if (cornerColor) {
@ -168,8 +169,7 @@ UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollectio
[strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1];
}
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *result = ASGraphicsGetImageAndEndCurrentContext();
UIEdgeInsets capInsets = UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius);
result = [result resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch];

View File

@ -283,14 +283,14 @@
CC55321D1E16EB7A0011C01F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
};
name = Debug;
};
CC55321E1E16EB7A0011C01F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
};
name = Release;
};
@ -336,7 +336,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = ASDKListKitTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
@ -382,7 +382,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = ASDKListKitTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = asyncdisplaykit.ASDKListKitTests;

View File

@ -1,6 +1,6 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
platform :ios, '9.0'
target 'ASDKListKitTests' do
pod 'Texture/IGListKit', :path => '../..'
pod 'OCMock', '~> 3.4'

View File

@ -38,14 +38,14 @@
[downloader downloadImageWithURL:URL
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
downloadProgress:nil
completion:^(id<ASImageContainerProtocol> _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) {
completion:^(id<ASImageContainerProtocol> _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) {
[firstExpectation fulfill];
}];
[downloader downloadImageWithURL:URL
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
downloadProgress:nil
completion:^(id<ASImageContainerProtocol> _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) {
completion:^(id<ASImageContainerProtocol> _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) {
[secondExpectation fulfill];
}];

View File

@ -23,6 +23,8 @@
#import <vector>
#import <OCMock/OCMock.h>
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import "ASDisplayNodeTestsHelper.h"
@interface ASTextCellNodeWithSetSelectedCounter : ASTextCellNode
@ -860,6 +862,7 @@
[cn waitUntilAllUpdatesAreProcessed];
[cn.view layoutIfNeeded];
ASCellNode *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASCATransactionQueueWait();
XCTAssertTrue(node.visible);
testController.asyncDelegate->_itemCounts = {0};
[cn deleteItemsAtIndexPaths: @[[NSIndexPath indexPathForItem:0 inSection:0]]];
@ -1047,7 +1050,7 @@
window.rootViewController = testController;
[window makeKeyAndVisible];
// Trigger the initial reload to start
// Trigger the initial reload to start
[window layoutIfNeeded];
// Test the APIs that monitor ASCollectionNode update handling
@ -1071,6 +1074,7 @@
for (NSInteger i = 0; i < c; i++) {
NSIndexPath *ip = [NSIndexPath indexPathForItem:i inSection:s];
ASCellNode *node = [cn nodeForItemAtIndexPath:ip];
ASCATransactionQueueWait();
if (node.inPreloadState) {
CGRect frame = [cn.view layoutAttributesForItemAtIndexPath:ip].frame;
r = CGRectUnion(r, frame);
@ -1097,7 +1101,7 @@
[window layoutIfNeeded];
// The initial reload is async, changing the trait collection here should be "mid-update"
ASPrimitiveTraitCollection traitCollection;
ASPrimitiveTraitCollection traitCollection = ASPrimitiveTraitCollectionMakeDefault();
traitCollection.displayScale = cn.primitiveTraitCollection.displayScale + 1; // Just a dummy change
traitCollection.containerSize = screenBounds.size;
cn.primitiveTraitCollection = traitCollection;

View File

@ -114,6 +114,7 @@
}
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
[node setHierarchyState:ASHierarchyStateRangeManaged];
node.automaticallyManagesSubnodes = YES;
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
ASAbsoluteLayoutSpec *absoluteLayout = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[subnodes[3]]];
@ -130,6 +131,7 @@
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)));
[node recursivelySetInterfaceState:ASInterfaceStatePreload];
ASCATransactionQueueWait();
// No premature view allocation
XCTAssertFalse(node.isNodeLoaded);
// Subnodes should be inserted, laid out and entered preload state

View File

@ -91,7 +91,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
@interface ASDisplayNode (HackForTests)
- (id)initWithViewClass:(Class)viewClass;
- (id)initWithLayerClass:(Class)layerClass;
- (void)setInterfaceState:(ASInterfaceState)state;
// FIXME: Importing ASDisplayNodeInternal.h causes a heap of problems.
- (void)enterInterfaceState:(ASInterfaceState)interfaceState;
@end
@ -127,6 +127,12 @@ for (ASDisplayNode *n in @[ nodes ]) {\
@implementation ASTestDisplayNode
- (void)setInterfaceState:(ASInterfaceState)state
{
[super setInterfaceState:state];
ASCATransactionQueueWait();
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
return _calculateSizeBlock ? _calculateSizeBlock(self, constrainedSize) : CGSizeZero;
@ -178,6 +184,28 @@ for (ASDisplayNode *n in @[ nodes ]) {\
@end
@interface ASSynchronousTestDisplayNodeViaViewClass : ASDisplayNode
@end
@implementation ASSynchronousTestDisplayNodeViaViewClass
+ (Class)viewClass {
return [UIView class];
}
@end
@interface ASSynchronousTestDisplayNodeViaLayerClass : ASDisplayNode
@end
@implementation ASSynchronousTestDisplayNodeViaLayerClass
+ (Class)layerClass {
return [CALayer class];
}
@end
@interface UIDisplayNodeTestView : UIView
@end
@ -2041,9 +2069,9 @@ static bool stringContainsPointer(NSString *description, id p) {
// Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205
- (void)testThatRasterizedNodesGetInterfaceStateUpdatesWhenContainerEntersHierarchy
{
ASDisplayNode *supernode = [[ASDisplayNode alloc] init];
ASDisplayNode *supernode = [[ASTestDisplayNode alloc] init];
[supernode enableSubtreeRasterization];
ASDisplayNode *subnode = [[ASDisplayNode alloc] init];
ASDisplayNode *subnode = [[ASTestDisplayNode alloc] init];
ASSetDebugNames(supernode, subnode);
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[supernode addSubnode:subnode];
@ -2059,9 +2087,9 @@ static bool stringContainsPointer(NSString *description, id p) {
// Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205
- (void)testThatRasterizedNodesGetInterfaceStateUpdatesWhenAddedToContainerThatIsInHierarchy
{
ASDisplayNode *supernode = [[ASDisplayNode alloc] init];
ASDisplayNode *supernode = [[ASTestDisplayNode alloc] init];
[supernode enableSubtreeRasterization];
ASDisplayNode *subnode = [[ASDisplayNode alloc] init];
ASDisplayNode *subnode = [[ASTestDisplayNode alloc] init];
ASSetDebugNames(supernode, subnode);
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
@ -2175,8 +2203,7 @@ static bool stringContainsPointer(NSString *description, id p) {
[node view]; // Node needs to be loaded
[node enterInterfaceState:ASInterfaceStatePreload];
XCTAssertTrue((node.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload);
XCTAssertTrue((subnode.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload);
XCTAssertTrue(node.hasPreloaded);
@ -2354,4 +2381,21 @@ static bool stringContainsPointer(NSString *description, id p) {
XCTAssert(hasVC);
}
- (void)testScreenScale
{
XCTAssertEqual(ASScreenScale(), UIScreen.mainScreen.scale);
}
- (void)testThatIfViewClassIsOverwrittenItsSynchronous
{
ASSynchronousTestDisplayNodeViaViewClass *node = [[ASSynchronousTestDisplayNodeViaViewClass alloc] init];
XCTAssertTrue([node isSynchronous], @"Node should be synchronous if viewClass is ovewritten and not a subclass of _ASDisplayView");
}
- (void)testThatIfLayerClassIsOverwrittenItsSynchronous
{
ASSynchronousTestDisplayNodeViaLayerClass *node = [[ASSynchronousTestDisplayNodeViaLayerClass alloc] init];
XCTAssertTrue([node isSynchronous], @"Node should be synchronous if viewClass is ovewritten and not a subclass of _ASDisplayView");
}
@end

View File

@ -28,5 +28,6 @@ BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block);
void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size);
void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange);
void ASCATransactionQueueWait(void);
ASDISPLAYNODE_EXTERN_C_END

View File

@ -18,6 +18,7 @@
#import "ASDisplayNodeTestsHelper.h"
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASRunLoopQueue.h>
#import <QuartzCore/QuartzCore.h>
@ -62,3 +63,14 @@ void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange)
CGSize sizeThatFits = [node layoutThatFits:sizeRange].size;
node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits};
}
void ASCATransactionQueueWait(void)
{
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1];
BOOL whileResult = YES;
while ([date timeIntervalSinceNow] > 0 &&
(whileResult = ![[ASCATransactionQueue sharedQueue] isEmpty])) {
[[NSRunLoop currentRunLoop] runUntilDate:
[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
}

View File

@ -238,7 +238,7 @@
// Simulate completion.
ASImageDownloaderCompletion completionBlock = [inv as_argumentAtIndexAsObject:5];
completionBlock([self _testImage], nil, nil);
completionBlock([self _testImage], nil, nil, nil);
});
NSNumber *imageIdentifier = @1;

View File

@ -2,8 +2,8 @@
// ASRunLoopQueueTests.m
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// Copyright (c) 2017-present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
@ -12,9 +12,21 @@
#import <XCTest/XCTest.h>
#import <AsyncDisplayKit/ASRunLoopQueue.h>
#import "ASDisplayNodeTestsHelper.h"
static NSTimeInterval const kRunLoopRunTime = 0.001; // Allow the RunLoop to run for one millisecond each time.
@interface QueueObject : NSObject <ASCATransactionQueueObserving>
@property (nonatomic, assign) BOOL queueObjectProcessed;
@end
@implementation QueueObject
- (void)prepareForCATransactionCommit
{
self.queueObjectProcessed = YES;
}
@end
@interface ASRunLoopQueueTests : XCTestCase
@end
@ -157,4 +169,24 @@ static NSTimeInterval const kRunLoopRunTime = 0.001; // Allow the RunLoop to run
XCTAssertTrue(queue.isEmpty);
}
- (void)testASCATransactionQueueDisable
{
ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init];
[queue disable];
QueueObject *object = [[QueueObject alloc] init];
[[ASCATransactionQueue sharedQueue] enqueue:object];
XCTAssertTrue([queue isEmpty]);
XCTAssertTrue([queue disabled]);
}
- (void)testASCATransactionQueueProcess
{
ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init];
QueueObject *object = [[QueueObject alloc] init];
[queue enqueue:object];
XCTAssertFalse(object.queueObjectProcessed);
ASCATransactionQueueWait();
XCTAssertTrue(object.queueObjectProcessed);
}
@end

View File

@ -0,0 +1,34 @@
//
// ASTraitCollectionTests.m
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <XCTest/XCTest.h>
#import <AsyncDisplayKit/ASTraitCollection.h>
@interface ASTraitCollectionTests : XCTestCase
@end
@implementation ASTraitCollectionTests
- (void)testPrimitiveContentSizeCategoryLifetime
{
ASPrimitiveContentSizeCategory primitiveContentSize;
@autoreleasepool {
// Make sure the compiler won't optimize string alloc/dealloc
NSString *contentSizeCategory = [NSString stringWithCString:"UICTContentSizeCategoryL" encoding:NSUTF8StringEncoding];
primitiveContentSize = ASPrimitiveContentSizeCategoryMake(contentSizeCategory);
}
XCTAssertEqual(primitiveContentSize, UIContentSizeCategoryLarge);
}
@end

View File

@ -21,6 +21,7 @@
#import <XCTest/XCTest.h>
#import <AVFoundation/AVFoundation.h>
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import "ASDisplayNodeTestsHelper.h"
@interface ASVideoNodeTests : XCTestCase <ASVideoNodeDelegate>
{
@ -351,9 +352,9 @@
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload];
[_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys];
ASCATransactionQueueWait();
[_videoNode pause];
_videoNode.shouldBePlaying = YES;
XCTAssertFalse(_videoNode.isPlaying);
[_videoNode observeValueForKeyPath:@"playbackLikelyToKeepUp" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @YES} context:NULL];

View File

@ -14,7 +14,7 @@ Pod::Spec.new do |spec|
spec.weak_frameworks = 'Photos','MapKit','AssetsLibrary'
spec.requires_arc = true
spec.ios.deployment_target = '8.0'
spec.ios.deployment_target = '9.0'
# Uncomment when fixed: issues with tvOS build for release 2.0
# spec.tvos.deployment_target = '9.0'
@ -51,7 +51,7 @@ Pod::Spec.new do |spec|
end
spec.subspec 'IGListKit' do |igl|
igl.dependency 'IGListKit', '3.0.0'
igl.dependency 'IGListKit', '~> 3.0'
igl.dependency 'Texture/Core'
end

View File

@ -137,7 +137,7 @@ optional public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditabl
</div>
</div>
--  Indicates to the delegate that teh text node has finished editing.
--  Indicates to the delegate that the text node has finished editing.
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>

View File

@ -394,17 +394,17 @@ Within `ASAbsoluteLayoutSpec` you can specify exact locations (x/y coordinates)
CGSize maxConstrainedSize = constrainedSize.max;
// Layout all nodes absolute in a static layout spec
guitarVideoNode.layoutPosition = CGPointMake(0, 0);
guitarVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width, maxConstrainedSize.height / 3.0));
guitarVideoNode.style.layoutPosition = CGPointMake(0, 0);
guitarVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width, maxConstrainedSize.height / 3.0));
nicCageVideoNode.layoutPosition = CGPointMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0);
nicCageVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0));
nicCageVideoNode.style.layoutPosition = CGPointMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0);
nicCageVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0));
simonVideoNode.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height - (maxConstrainedSize.height / 3.0));
simonVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width/2, maxConstrainedSize.height / 3.0));
simonVideoNode.style.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height - (maxConstrainedSize.height / 3.0));
simonVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width/2, maxConstrainedSize.height / 3.0));
hlsVideoNode.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height / 3.0);
hlsVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0));
hlsVideoNode.style.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height / 3.0);
hlsVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0));
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[guitarVideoNode, nicCageVideoNode, simonVideoNode, hlsVideoNode]];
}

View File

@ -51,7 +51,7 @@ An `ASViewController` is a regular `UIViewController` subclass that has special
### `-init`
This method is called once, at the very begining of an ASViewController's lifecycle. As with UIViewController initialization, it is best practice to **never access** `self.view` or `self.node.view` in this method as it will force the view to be created early. Instead, do any view access in -viewDidLoad.
This method is called once, at the very beginning of an ASViewController's lifecycle. As with UIViewController initialization, it is best practice to **never access** `self.view` or `self.node.view` in this method as it will force the view to be created early. Instead, do any view access in -viewDidLoad.
ASViewController's designated initializer is `initWithNode:`. A typical initializer will look something like the code below. Note how the ASViewController's node is created _before_ calling super. An ASViewController manages a node similarly to how a UIViewController manages a view, but the initialization is slightly different.

View File

@ -229,6 +229,16 @@ permalink: /showcase.html
</tr>
<tr>
<td align="center" valign="top">
<a href="https://itunes.apple.com/in/app/mensxp-fashion-grooming-tips/id1253494246?mt=8"><img class="roundrect" src="http://is5.mzstatic.com/image/thumb/Purple118/v4/04/78/65/04786577-67dc-ea87-2063-b97a89492e18/source/175x175bb.jpg" style="width:100px;height:100px;"></a>
<br />
<b>MensXP</b>
</td>
</tr>
</table>
<br />

View File

@ -1,5 +1,5 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
platform :ios, '9.0'
target 'Sample' do
pod 'Texture', :path => '../..'
end

View File

@ -240,13 +240,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@ -302,7 +305,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -338,7 +341,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -353,7 +356,6 @@
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = 1;
@ -367,7 +369,6 @@
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = 1;

View File

@ -23,10 +23,13 @@
#define ASYNC_COLLECTION_LAYOUT 0
static CGSize const kItemSize = (CGSize){180, 90};
@interface ViewController () <ASCollectionDataSource, ASCollectionDelegateFlowLayout, ASCollectionGalleryLayoutPropertiesProviding>
@property (nonatomic, strong) ASCollectionNode *collectionNode;
@property (nonatomic, strong) NSArray *data;
@property (nonatomic, strong) NSMutableArray<NSMutableArray<NSString *> *> *data;
@property (nonatomic, strong) UILongPressGestureRecognizer *moveRecognizer;
@end
@ -34,18 +37,13 @@
#pragma mark - Lifecycle
- (void)dealloc
{
self.collectionNode.dataSource = nil;
self.collectionNode.delegate = nil;
NSLog(@"ViewController is deallocing");
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.moveRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress)];
[self.view addGestureRecognizer:self.moveRecognizer];
#if ASYNC_COLLECTION_LAYOUT
ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionVerticalDirections];
layoutDelegate.propertiesProvider = self;
@ -54,6 +52,7 @@
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.headerReferenceSize = CGSizeMake(50.0, 50.0);
layout.footerReferenceSize = CGSizeMake(50.0, 50.0);
layout.itemSize = kItemSize;
self.collectionNode = [[ASCollectionNode alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
[self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
[self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter];
@ -73,34 +72,37 @@
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh
target:self
action:@selector(reloadTapped)];
#endif
#if SIMULATE_WEB_RESPONSE
[self loadData];
#else
__weak typeof(self) weakSelf = self;
void(^mockWebService)() = ^{
NSLog(@"ViewController \"got data from a web service\"");
ViewController *strongSelf = weakSelf;
if (strongSelf != nil)
{
NSLog(@"ViewController is not nil");
strongSelf->_data = [[NSArray alloc] init];
[strongSelf->_collectionNode performBatchUpdates:^{
[strongSelf->_collectionNode insertSections:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, 100)]];
} completion:nil];
NSLog(@"ViewController finished updating collectionNode");
}
else {
NSLog(@"ViewController is nil - won't update collectionNode");
}
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), mockWebService);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf handleSimulatedWebResponse];
});
#endif
}
- (void)handleSimulatedWebResponse
{
[self.collectionNode performBatchUpdates:^{
[self loadData];
[self.collectionNode insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.data.count)]];
} completion:nil];
}
- (void)loadData
{
// Form our data array
typeof(self.data) data = [NSMutableArray array];
for (NSInteger s = 0; s < 100; s++) {
NSMutableArray *items = [NSMutableArray array];
for (NSInteger i = 0; i < 10; i++) {
items[i] = [NSString stringWithFormat:@"[%zd.%zd] says hi", s, i];
}
data[s] = items;
}
self.data = data;
}
#pragma mark - Button Actions
- (void)reloadTapped
@ -115,14 +117,42 @@
- (CGSize)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(ASElementMap *)elements
{
ASDisplayNodeAssertMainThread();
return CGSizeMake(180, 90);
return kItemSize;
}
#pragma mark - ASCollectionView Data Source
- (void)handleLongPress
{
UICollectionView *collectionView = self.collectionNode.view;
CGPoint location = [self.moveRecognizer locationInView:collectionView];
switch (self.moveRecognizer.state) {
case UIGestureRecognizerStateBegan: {
NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:location];
if (indexPath) {
[collectionView beginInteractiveMovementForItemAtIndexPath:indexPath];
}
break;
}
case UIGestureRecognizerStateChanged:
[collectionView updateInteractiveMovementTargetPosition:location];
break;
case UIGestureRecognizerStateEnded:
[collectionView endInteractiveMovement];
break;
case UIGestureRecognizerStateFailed:
case UIGestureRecognizerStateCancelled:
[collectionView cancelInteractiveMovement];
break;
case UIGestureRecognizerStatePossible:
// nop
break;
}
}
#pragma mark - ASCollectionDataSource
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath;
{
NSString *text = [NSString stringWithFormat:@"[%zd.%zd] says hi", indexPath.section, indexPath.item];
NSString *text = self.data[indexPath.section][indexPath.item];
return ^{
return [[ItemNode alloc] initWithString:text];
};
@ -139,18 +169,29 @@
- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section
{
return 10;
return self.data[section].count;
}
- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode
{
#if SIMULATE_WEB_RESPONSE
return _data == nil ? 0 : 100;
#else
return 100;
#endif
return self.data.count;
}
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canMoveItemWithNode:(ASCellNode *)node
{
return YES;
}
- (void)collectionNode:(ASCollectionNode *)collectionNode moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
__auto_type sectionArray = self.data[sourceIndexPath.section];
__auto_type object = sectionArray[sourceIndexPath.item];
[sectionArray removeObjectAtIndex:sourceIndexPath.item];
[self.data[destinationIndexPath.section] insertObject:object atIndex:destinationIndexPath.item];
}
#pragma mark - ASCollectionDelegate
- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context
{
NSLog(@"fetch additional content");

View File

@ -1,5 +1,5 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
platform :ios, '9.0'
target 'Sample' do
pod 'Texture', :path => '../..'
pod 'Texture/Yoga', :path => '../..'

View File

@ -196,13 +196,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = {
@ -271,7 +274,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -306,7 +309,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;

View File

@ -1,5 +1,5 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
platform :ios, '9.0'
target 'Sample' do
pod 'Texture', :path => '../..'
end

View File

@ -277,13 +277,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = {
@ -356,7 +359,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -391,7 +394,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;

View File

@ -1,5 +1,5 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
platform :ios, '9.0'
target 'Sample' do
pod 'Texture/IGListKit', :path => '../..'
pod 'Texture/PINRemoteImage', :path => '../..'

View File

@ -381,13 +381,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = {
@ -477,7 +480,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -512,7 +515,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;

View File

@ -22,6 +22,8 @@
#import "WindowWithStatusBarUnderlay.h"
#import "Utilities.h"
#import <AsyncDisplayKit/ASGraphicsContext.h>
#define WEAVER 0
#if WEAVER
@ -38,6 +40,8 @@
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
ASEnableNoCopyRendering();
// this UIWindow subclass is neccessary to make the status bar opaque
_window = [[WindowWithStatusBarUnderlay alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
_window.backgroundColor = [UIColor whiteColor];

View File

@ -44,6 +44,9 @@
#define InsetForHeader UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER)
#define InsetForFooter UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER)
@interface PhotoCellNode () <ASNetworkImageNodeDelegate>
@end
@implementation PhotoCellNode
{
PhotoModel *_photoModel;
@ -77,6 +80,7 @@
}];
_photoImageNode = [[ASNetworkImageNode alloc] init];
_photoImageNode.delegate = self;
_photoImageNode.URL = photo.URL;
_photoImageNode.layerBacked = YES;
@ -284,6 +288,19 @@
}];
}
#pragma mark - Network Image Delegate
- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageLoadInfo *)info
{
// Docs say method is called from bg but right now it's called from main.
// Save main thread time by shunting this.
if (info.sourceType == ASNetworkImageSourceDownload) {
ASPerformBlockOnBackgroundThread(^{
NSLog(@"Received image %@ from %@ with userInfo %@", image, info.url.path, ASObjectDescriptionMakeTiny(info.userInfo));
});
}
}
#pragma mark - Helper Methods
- (ASTextNode *)createLayerBackedTextNodeWithString:(NSAttributedString *)attributedString

View File

@ -1,20 +1,18 @@
//
// PhotoFeedModel.m
// Sample
//
// Created by Hannah Troisi on 2/28/16.
// Texture
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import "PhotoFeedModel.h"
@ -184,9 +182,11 @@
// early return if reached end of pages
if (_totalPages) {
if (_currentPage == _totalPages) {
if (block){
block(@[]);
}
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block(@[]);
}
});
return;
}
}

View File

@ -1,5 +1,5 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
platform :ios, '9.0'
target 'Sample' do
pod 'Texture', :path => '../..'
end

View File

@ -214,13 +214,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@ -288,7 +291,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -325,7 +328,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;

View File

@ -1,5 +1,5 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
platform :ios, '9.0'
target 'Sample' do
pod 'Texture', :path => '../..'
end

View File

@ -219,13 +219,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@ -294,7 +297,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -331,7 +334,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;

View File

@ -1,5 +1,5 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
platform :ios, '9.0'
target 'Sample' do
pod 'Texture', :path => '../..'
pod 'PINRemoteImage/WebP'

View File

@ -214,13 +214,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */

View File

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '8.0'
platform :ios, '9.0'
# Uncomment this line if you're using Swift
# use_frameworks!

View File

@ -217,13 +217,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
84F93825AFB1CA7FBB116BA4 /* [CP] Copy Pods Resources */ = {
@ -309,7 +312,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -348,7 +351,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";

View File

@ -1,5 +1,5 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
platform :ios, '9.0'
target 'Sample' do
pod 'Texture', :path => '../..'
end

Some files were not shown because too many files have changed in this diff Show More