mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-15 18:59:54 +00:00
Merge commit '2618c50073092e99fe1df5213ba6a0619e908988'
# Conflicts: # AsyncDisplayKit.xcodeproj/project.pbxproj # Source/Private/ASDisplayNode+AsyncDisplay.mm
This commit is contained in:
commit
3a2c2ea690
@ -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 */,
|
||||
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@ -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)
|
||||
|
||||
2
Cartfile
2
Cartfile
@ -1,2 +1,2 @@
|
||||
github "pinterest/PINRemoteImage" "3.0.0-beta.13"
|
||||
github "pinterest/PINCache"
|
||||
github "pinterest/PINCache" "3.0.1-beta.6"
|
||||
|
||||
10
Dangerfile
10
Dangerfile
@ -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
|
||||
|
||||
2
Podfile
2
Podfile
@ -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'
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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:.
|
||||
*
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
|
||||
@ -219,6 +219,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (BOOL)editableTextNodeShouldPaste:(ASEditableTextNode *)editableTextNode;
|
||||
- (ASEditableTextNodeTargetForAction * _Nullable)editableTextNodeTargetForAction:(SEL)action;
|
||||
- (BOOL)editableTextNodeShouldReturn:(ASEditableTextNode *)editableTextNode;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
39
Source/ASNetworkImageLoadInfo.h
Normal file
39
Source/ASNetworkImageLoadInfo.h
Normal 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
|
||||
32
Source/ASNetworkImageLoadInfo.m
Normal file
32
Source/ASNetworkImageLoadInfo.m
Normal 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
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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.")));
|
||||
|
||||
@ -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];
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
65
Source/Details/ASGraphicsContext.h
Normal file
65
Source/Details/ASGraphicsContext.h
Normal 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
|
||||
194
Source/Details/ASGraphicsContext.m
Normal file
194
Source/Details/ASGraphicsContext.m
Normal 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();
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@ -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
|
||||
}];
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -77,7 +77,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
||||
{
|
||||
@package
|
||||
_ASPendingState *_pendingViewState;
|
||||
|
||||
ASInterfaceState _pendingInterfaceState;
|
||||
UIView *_view;
|
||||
CALayer *_layer;
|
||||
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
22
Source/Private/ASNetworkImageLoadInfo+Private.h
Normal file
22
Source/Private/ASNetworkImageLoadInfo+Private.h
Normal 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
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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`.
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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];
|
||||
}];
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
34
Tests/ASTraitCollectionTests.m
Normal file
34
Tests/ASTraitCollectionTests.m
Normal 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
|
||||
@ -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];
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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]];
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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 => '../..'
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 => '../..'
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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!
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user