diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 12e50d0543..2b412a4527 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -10,14 +10,14 @@ 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */; }; 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */; }; 044284FF1BAA3BD600D16268 /* UICollectionViewLayout+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 044285071BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; settings = {ASSET_TAGS = (); }; }; - 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; settings = {ASSET_TAGS = (); }; }; - 044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; settings = {ASSET_TAGS = (); }; }; - 0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; settings = {ASSET_TAGS = (); }; }; - 0442850D1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; settings = {ASSET_TAGS = (); }; }; - 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; settings = {ASSET_TAGS = (); }; }; - 0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; settings = {ASSET_TAGS = (); }; }; - 044285101BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; settings = {ASSET_TAGS = (); }; }; + 044285071BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; }; + 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; }; + 044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; }; + 0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; }; + 0442850D1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; }; + 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; }; + 0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; }; + 044285101BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; }; 0515EA211A15769900BA8B9A /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 0515EA221A1576A100BA8B9A /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; 0516FA3C1A15563400B4EBED /* ASAvailability.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3A1A15563400B4EBED /* ASAvailability.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -67,7 +67,6 @@ 058D0A22195D050800B7D73C /* _ASAsyncTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F9195D050800B7D73C /* _ASAsyncTransaction.m */; }; 058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */; }; 058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */; }; - 058D0A25195D050800B7D73C /* UIView+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A00195D050800B7D73C /* UIView+ASConvenience.m */; }; 058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */; }; 058D0A27195D050800B7D73C /* _ASPendingState.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A06195D050800B7D73C /* _ASPendingState.m */; }; 058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */; }; @@ -140,14 +139,14 @@ 205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; }; - 251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; settings = {ASSET_TAGS = (); }; }; - 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; settings = {ASSET_TAGS = (); }; }; - 251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ASSET_TAGS = (); }; }; - 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; settings = {ASSET_TAGS = (); }; }; - 251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; settings = {ASSET_TAGS = (); }; }; - 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; settings = {ASSET_TAGS = (); }; }; + 251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; }; + 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; + 251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; }; + 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; + 251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; }; + 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; }; 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */; settings = {ASSET_TAGS = (); }; }; + 2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */; }; 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; }; 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; 292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -162,7 +161,7 @@ 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; }; 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */; }; 2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; settings = {ASSET_TAGS = (); }; }; + 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; }; 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED071B17843500DA7C62 /* ASDimension.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED081B17843500DA7C62 /* ASDimension.mm */; }; 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; }; @@ -217,8 +216,8 @@ 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C5586691BD549CB00B50E3A /* ASAsciiArtBoxCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; settings = {ASSET_TAGS = (); }; }; - 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; settings = {ASSET_TAGS = (); }; }; + 9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; }; + 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; }; 9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C5FA3511B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5FA34F1B8F6ADF00A62714 /* ASLayoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C5FA3521B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5FA34F1B8F6ADF00A62714 /* ASLayoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -232,11 +231,12 @@ 9C6BB3B31B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; - 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; settings = {ASSET_TAGS = (); }; }; - 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; settings = {ASSET_TAGS = (); }; }; + 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; + 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; 9CDC18CC1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9CDC18CD1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */; }; + AC026B581BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */; }; AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */; }; @@ -245,7 +245,6 @@ AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */; }; AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */; }; AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */; }; - AC026B581BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */; }; AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A501A1139C100143C57 /* ASCollectionView.mm */; }; @@ -372,7 +371,6 @@ B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */; }; B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B35062441B010EFD0018CF92 /* UIView+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A00195D050800B7D73C /* UIView+ASConvenience.m */; }; B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */; }; B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A02195D050800B7D73C /* _AS-objc-internal.h */; }; B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */; }; @@ -399,12 +397,16 @@ B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; settings = {ASSET_TAGS = (); }; }; - CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; settings = {ASSET_TAGS = (); }; }; + CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; }; + CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; }; CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; + DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; + DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -607,11 +609,11 @@ 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackBaselinePositionedLayout.mm; sourceTree = ""; }; 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutablePrivate.h; path = AsyncDisplayKit/Layout/ASLayoutablePrivate.h; sourceTree = ""; }; 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewTests.m; sourceTree = ""; }; + AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASStaticLayoutSpecSnapshotTests.m; sourceTree = ""; }; AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASChangeSetDataController.h; sourceTree = ""; }; AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASChangeSetDataController.m; sourceTree = ""; }; AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASHierarchyChangeSet.h; sourceTree = ""; }; AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASHierarchyChangeSet.m; sourceTree = ""; }; - AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASStaticLayoutSpecSnapshotTests.m; sourceTree = ""; }; AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutDefines.h; path = AsyncDisplayKit/Layout/ASStackLayoutDefines.h; sourceTree = ""; }; AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionView.h; sourceTree = ""; }; AC3C4A501A1139C100143C57 /* ASCollectionView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionView.mm; sourceTree = ""; }; @@ -667,6 +669,8 @@ 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 = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; + DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; + DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -788,6 +792,8 @@ AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */, 058D09D5195D050800B7D73C /* ASControlNode.h */, 058D09D6195D050800B7D73C /* ASControlNode.m */, + DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */, + DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */, 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */, 058D09D8195D050800B7D73C /* ASDisplayNode.h */, 058D09D9195D050800B7D73C /* ASDisplayNode.mm */, @@ -1129,6 +1135,7 @@ 05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */, ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */, 058D0A78195D05F900B7D73C /* ASDisplayNode+DebugTiming.h in Headers */, + DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */, 058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */, 058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */, 058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */, @@ -1267,6 +1274,7 @@ B350625C1B010F070018CF92 /* ASLog.h in Headers */, 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */, B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */, + DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */, B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */, B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */, 34EFC76C1B701CED00AD841F /* ASOverlayLayoutSpec.h in Headers */, @@ -1355,6 +1363,7 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, + 6CC5F540055A48FCA8C12BF5 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1391,7 +1400,7 @@ 058D09A4195D04C000B7D73C /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0510; + LastUpgradeCheck = 0710; ORGANIZATIONNAME = Facebook; TargetAttributes = { 057D02BE1AC0A66700C7AC3C = { @@ -1484,6 +1493,21 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; + 6CC5F540055A48FCA8C12BF5 /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1541,6 +1565,7 @@ 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */, ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */, 0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */, + DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */, 058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */, 055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */, ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */, @@ -1657,6 +1682,7 @@ 34EFC7681B701CDE00AD841F /* ASLayout.mm in Sources */, 9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */, 9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */, + DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */, 34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */, B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */, B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */, @@ -1771,6 +1797,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_TEST_COVERAGE_FILES = YES; diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme index df6da9c539..c40014ed91 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme @@ -1,6 +1,6 @@ + +typedef enum : NSUInteger { + ASButtonStateNormal, + ASButtonStateHighlighted, + ASButtonStateDisabled, +} ASButtonState; + +@interface ASButtonNode : ASControlNode + +@property (nonatomic, readonly) ASTextNode *titleNode; +@property (nonatomic, readonly) ASImageNode *imageNode; + +/** + Spacing between image and title. Defaults to 8.0. + */ +@property (nonatomic, assign) CGFloat contentSpacing; + +/** + Whether button should be laid out vertically (image on top of text) or horizontally (image to the left of text). + ASButton node does not yet support RTL but it should be fairly easy to implement. + Defaults to YES. + */ +@property (nonatomic, assign) BOOL laysOutHorizontally; + +- (NSAttributedString *)attributedTitleForState:(ASButtonState)state; +- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASButtonState)state; + +- (UIImage *)imageForState:(ASButtonState)state; +- (void)setImage:(UIImage *)image forState:(ASButtonState)state; + +@end diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm new file mode 100644 index 0000000000..1801a03527 --- /dev/null +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -0,0 +1,222 @@ +/* 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. + */ + +#import "ASButtonNode.h" + +#import + +@interface ASButtonNode () +{ + ASDN::RecursiveMutex _propertyLock; + + NSAttributedString *_normalAttributedTitle; + NSAttributedString *_highlightedAttributedTitle; + NSAttributedString *_disabledAttributedTitle; + + UIImage *_normalImage; + UIImage *_highlightedImage; + UIImage *_disabledImage; +} + +@end + +@implementation ASButtonNode + +@synthesize contentSpacing = _contentSpacing; +@synthesize laysOutHorizontally = _laysOutHorizontally; + +- (instancetype)init +{ + if (self = [super init]) { + _contentSpacing = 8.0; + _laysOutHorizontally = YES; + + _titleNode = [[ASTextNode alloc] init]; + _imageNode = [[ASImageNode alloc] init]; + + [self addSubnode:_titleNode]; + [self addSubnode:_imageNode]; + + [self addTarget:self action:@selector(controlEventUpdated:) forControlEvents:ASControlNodeEventAllEvents]; + } + return self; +} + +- (void)controlEventUpdated:(ASControlNode *)node +{ + [self updateImage]; + [self updateTitle]; +} + +- (void)updateImage +{ + ASDN::MutexLocker l(_propertyLock); + + UIImage *newImage; + if (self.enabled == NO && _disabledImage) { + newImage = _disabledImage; + } else if (self.highlighted && _highlightedImage) { + newImage = _highlightedImage; + } else { + newImage = _normalImage; + } + + if (newImage != self.imageNode.image) { + self.imageNode.image = newImage; + [self setNeedsLayout]; + } +} + +- (void)updateTitle +{ + ASDN::MutexLocker l(_propertyLock); + NSAttributedString *newTitle; + if (self.enabled == NO && _disabledAttributedTitle) { + newTitle = _disabledAttributedTitle; + } else if (self.highlighted && _highlightedAttributedTitle) { + newTitle = _highlightedAttributedTitle; + } else { + newTitle = _normalAttributedTitle; + } + + if (newTitle != self.titleNode.attributedString) { + self.titleNode.attributedString = newTitle; + [self setNeedsLayout]; + } +} + +- (CGFloat)contentSpacing +{ + ASDN::MutexLocker l(_propertyLock); + return _contentSpacing; +} + +- (void)setContentSpacing:(CGFloat)contentSpacing +{ + ASDN::MutexLocker l(_propertyLock); + if (contentSpacing == _contentSpacing) + return; + + _contentSpacing = contentSpacing; + [self setNeedsLayout]; +} + +- (BOOL)laysOutHorizontally +{ + ASDN::MutexLocker l(_propertyLock); + return _laysOutHorizontally; +} + +- (void)setLaysOutHorizontally:(BOOL)laysOutHorizontally +{ + ASDN::MutexLocker l(_propertyLock); + if (laysOutHorizontally == _laysOutHorizontally) + return; + + _laysOutHorizontally = laysOutHorizontally; + [self setNeedsLayout]; +} + +- (NSAttributedString *)attributedTitleForState:(ASButtonState)state +{ + ASDN::MutexLocker l(_propertyLock); + switch (state) { + case ASButtonStateNormal: + return _normalAttributedTitle; + + case ASButtonStateHighlighted: + return _highlightedAttributedTitle; + + case ASButtonStateDisabled: + return _disabledAttributedTitle; + } +} + +- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASButtonState)state +{ + ASDN::MutexLocker l(_propertyLock); + switch (state) { + case ASButtonStateNormal: + _normalAttributedTitle = [title copy]; + break; + + case ASButtonStateHighlighted: + _highlightedAttributedTitle = [title copy]; + break; + + case ASButtonStateDisabled: + _disabledAttributedTitle = [title copy]; + break; + } + [self updateTitle]; +} + +- (UIImage *)imageForState:(ASButtonState)state +{ + ASDN::MutexLocker l(_propertyLock); + switch (state) { + case ASButtonStateNormal: + return _normalImage; + + case ASButtonStateHighlighted: + return _highlightedImage; + + case ASButtonStateDisabled: + return _disabledImage; + } +} + +- (void)setImage:(UIImage *)image forState:(ASButtonState)state +{ + ASDN::MutexLocker l(_propertyLock); + switch (state) { + case ASButtonStateNormal: + _normalImage = image; + break; + + case ASButtonStateHighlighted: + _highlightedImage = image; + break; + + case ASButtonStateDisabled: + _disabledImage = image; + break; + } + [self updateImage]; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init]; + stack.direction = self.laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; + stack.spacing = self.contentSpacing; + stack.justifyContent = ASStackLayoutJustifyContentCenter; + stack.alignItems = ASStackLayoutAlignItemsCenter; + + NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; + if (self.imageNode.image) { + [children addObject:self.imageNode]; + } + + if (self.titleNode.attributedString.length > 0) { + [children addObject:self.titleNode]; + } + + stack.children = children; + + return stack; +} + +- (void)layout +{ + [super layout]; + self.imageNode.hidden = self.imageNode.image == nil; + self.titleNode.hidden = self.titleNode.attributedString.length > 0 == NO; +} + +@end diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index 3acef17ce5..c7a5e6041d 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -10,6 +10,23 @@ NS_ASSUME_NONNULL_BEGIN +@class ASCellNode; + +typedef NSUInteger ASCellNodeAnimation; + +@protocol ASCellNodeLayoutDelegate + +/** + * Notifies the delegate that the specified cell node has done a relayout. + * The notification is done on main thread. + * + * @param node A node informing the delegate about the relayout. + * + * @param suggestedAnimation A constant indicates how the delegate should animate. See UITableViewRowAnimation. + */ +- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation; +@end + /** * Generic cell node. Subclass this instead of `ASDisplayNode` to use with `ASTableView` and `ASCollectionView`. */ @@ -54,6 +71,18 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) BOOL highlighted; +/* + * A delegate to be notified (on main thread) after a relayout. + */ +@property (nonatomic, weak) id layoutDelegate; + +/* + * A constant that is passed to the delegate to indicate how a relayout is to be animated. + * + * @see UITableViewRowAnimation + */ +@property (nonatomic, assign) ASCellNodeAnimation relayoutAnimation; + /* * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding * these methods (e.g. for highlighting) requires the super method be called. @@ -63,6 +92,17 @@ NS_ASSUME_NONNULL_BEGIN - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; +/** + * Marks the node as needing layout. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread. + * + * If this node was measured, calling this method triggers an internal relayout: the calculated layout is invalidated, + * and the supernode is notified or (if this node is the root one) a full measurement pass is executed using the old constrained size. + * The delegate will then be notified on main thread. + * + * This method can be called inside of an animation block (to animate all of the layout changes). + */ +- (void)setNeedsLayout; + @end diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index ae6b3f6761..a56e9f3501 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -8,6 +8,7 @@ #import "ASCellNode.h" +#import "ASInternalHelpers.h" #import #import #import @@ -27,6 +28,7 @@ // use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; self.clipsToBounds = YES; + _relayoutAnimation = UITableViewRowAnimationAutomatic; return self; } @@ -49,6 +51,18 @@ ASDisplayNodeAssert(!layerBacked, @"ASCellNode does not support layer-backing."); } +- (void)setNeedsLayout +{ + ASDisplayNodeAssertThreadAffinity(self); + [super setNeedsLayout]; + + if (_layoutDelegate != nil) { + ASPerformBlockOnMainThread(^{ + [_layoutDelegate node:self didRelayoutWithSuggestedAnimation:_relayoutAnimation]; + }); + } +} + - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { ASDisplayNodeAssertMainThread(); @@ -122,7 +136,7 @@ static const CGFloat kFontSize = 18.0f; _text = [text copy]; _textNode.attributedString = [[NSAttributedString alloc] initWithString:_text attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kFontSize]}]; - + [self setNeedsLayout]; } @end diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 749b987a13..b10a274675 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -225,16 +225,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths; -/** - * Relayouts the specified item. - * - * @param indexPath The index path identifying the item to relayout. - * - * @discussion This method must be called from the main thread. The relayout is excuted on main thread. - * The node of the specified item must be updated to cause layout changes before this method is called. - */ -- (void)relayoutItemAtIndexPath:(NSIndexPath *)indexPath; - /** * Moves the item at a specified location to a destination location. * diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index c123914ab7..f7516a264a 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -12,7 +12,6 @@ #import "ASCollectionViewLayoutController.h" #import "ASRangeController.h" #import "ASCollectionDataController.h" -#import "ASDisplayNodeInternal.h" #import "ASBatchFetching.h" #import "UICollectionViewLayout+ASConvenience.h" #import "ASInternalHelpers.h" @@ -131,7 +130,7 @@ static BOOL _isInterceptedSelector(SEL sel) #pragma mark - #pragma mark ASCollectionView. -@interface ASCollectionView () { +@interface ASCollectionView () { _ASCollectionViewProxy *_proxyDataSource; _ASCollectionViewProxy *_proxyDelegate; @@ -269,7 +268,7 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)reloadDataWithCompletion:(void (^)())completion { ASDisplayNodeAssert(self.asyncDelegate, @"ASCollectionView's asyncDelegate property must be set."); - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _superIsPendingDataLoad = YES; [super reloadData]; }); @@ -458,14 +457,6 @@ static BOOL _isInterceptedSelector(SEL sel) [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; } -- (void)relayoutItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - [node setNeedsLayout]; - [super reloadItemsAtIndexPaths:@[indexPath]]; -} - - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); @@ -664,6 +655,7 @@ static BOOL _isInterceptedSelector(SEL sel) { ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); + node.layoutDelegate = self; return node; } @@ -789,26 +781,14 @@ static BOOL _isInterceptedSelector(SEL sel) } return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - - BOOL animationsEnabled = NO; - - if (!animated) { - animationsEnabled = [UIView areAnimationsEnabled]; - [UIView setAnimationsEnabled:NO]; - } - - [super performBatchUpdates:^{ - [_batchUpdateBlocks enumerateObjectsUsingBlock:^(dispatch_block_t block, NSUInteger idx, BOOL *stop) { - block(); - }]; - } completion:^(BOOL finished) { - if (!animated) { - [UIView setAnimationsEnabled:animationsEnabled]; - } - if (completion) { - completion(finished); - } - }]; + + ASPerformBlockWithoutAnimation(!animated, ^{ + [super performBatchUpdates:^{ + for (dispatch_block_t block in _batchUpdateBlocks) { + block(); + } + } completion:completion]; + }); [_batchUpdateBlocks removeAllObjects]; _performingBatchUpdates = NO; @@ -907,4 +887,15 @@ static BOOL _isInterceptedSelector(SEL sel) } } +#pragma mark - ASCellNodeDelegate + +- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation +{ + ASDisplayNodeAssertMainThread(); + NSIndexPath *indexPath = [self indexPathForNode:node]; + if (indexPath != nil) { + [super reloadItemsAtIndexPaths:@[indexPath]]; + } +} + @end diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 09d57a5072..0acc33937d 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -561,14 +561,6 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node); * * If this node was measured, calling this method triggers an internal relayout: the calculated layout is invalidated, * and the supernode is notified or (if this node is the root one) a full measurement pass is executed using the old constrained size. - * - * Note: If the relayout causes a change in size of the root node that is attached to a container view, - * the container view must be notified to relayout. - * For ASTableView and ASCollectionView, instead of calling this method directly, - * it is recommended to call -relayoutRowAtIndexPath:withRowAnimation and -relayoutItemAtIndexPath: respectively. - * - * @see [ASTableView relayoutRowAtIndexPath:withRowAnimation:] - * @see [ASCollectionView relayoutItemAtIndexPath:] */ - (void)setNeedsLayout; diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index c299cb0d9c..dde440f2e2 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -57,17 +57,6 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); } -void ASDisplayNodePerformBlockOnMainThread(void (^block)()) -{ - if ([NSThread isMainThread]) { - block(); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - block(); - }); - } -} - void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)()) { ASDisplayNodeCAssertNotNil(block, @"block is required"); @@ -79,7 +68,7 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block) // Hold the lock to avoid a race where the node gets loaded while the block is in-flight. ASDN::MutexLocker l(node->_propertyLock); if (node.nodeLoaded) { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ block(); }); } else { diff --git a/AsyncDisplayKit/ASEditableTextNode.h b/AsyncDisplayKit/ASEditableTextNode.h index 4a810f15d6..fff4a6d85b 100644 --- a/AsyncDisplayKit/ASEditableTextNode.h +++ b/AsyncDisplayKit/ASEditableTextNode.h @@ -60,6 +60,11 @@ NS_ASSUME_NONNULL_BEGIN //! @abstract The text input mode used by the receiver's keyboard, if it is visible. This value is undefined if the receiver is not the first responder. @property (nonatomic, readonly) UITextInputMode *textInputMode; +/* + @abstract The textContainerInset of both the placeholder and typed textView. This value defaults to UIEdgeInsetsZero. + */ +@property (nonatomic, readwrite) UIEdgeInsets textContainerInset; + /* @abstract The returnKeyType of the keyboard. This value defaults to UIReturnKeyDefault. */ diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index 5b929ccfb3..b0beb67385 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -78,7 +78,8 @@ _textKitComponents.layoutManager.delegate = self; _wordKerner = [[ASTextNodeWordKerner alloc] init]; _returnKeyType = UIReturnKeyDefault; - + _textContainerInset = UIEdgeInsetsZero; + // Create the placeholder scaffolding. _placeholderTextKitComponents = [ASTextKitComponents componentsWithAttributedSeedString:nil textContainerSize:CGSizeZero]; _placeholderTextKitComponents.layoutManager.delegate = self; @@ -122,7 +123,7 @@ textView.backgroundColor = nil; textView.opaque = NO; } - textView.textContainerInset = UIEdgeInsetsZero; + textView.textContainerInset = self.textContainerInset; textView.clipsToBounds = NO; // We don't want selection handles cut off. }; @@ -175,6 +176,15 @@ _placeholderTextKitComponents.textView.backgroundColor = backgroundColor; } +- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset +{ + ASDN::MutexLocker l(_textKitLock); + + _textContainerInset = textContainerInset; + _textKitComponents.textView.textContainerInset = textContainerInset; + _placeholderTextKitComponents.textView.textContainerInset = textContainerInset; +} + - (void)setOpaque:(BOOL)opaque { [super setOpaque:opaque]; diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 9d40a38e71..3191b53ab7 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -128,7 +128,7 @@ _image = image; ASDN::MutexUnlocker u(_imageLock); - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [self invalidateCalculatedLayout]; [self setNeedsDisplay]; }); @@ -306,7 +306,7 @@ // If we have an image to display, display it, respecting our recrop flag. if (self.image) { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ if (recropImmediately) [self displayImmediately]; else @@ -334,7 +334,7 @@ BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height)); // Re-display if we need to. - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage) [self setNeedsDisplay]; }); diff --git a/AsyncDisplayKit/ASNetworkImageNode.h b/AsyncDisplayKit/ASNetworkImageNode.h index b3961e1060..1d7134ca64 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.h +++ b/AsyncDisplayKit/ASNetworkImageNode.h @@ -24,10 +24,10 @@ NS_ASSUME_NONNULL_BEGIN @interface ASNetworkImageNode : ASImageNode /** - * The designated initializer. + * The designated initializer. Cache and Downloader are WEAK references. * - * @param cache The object that implements a cache of images for the image node. - * @param downloader The object that implements image downloading for the image node. Must not be nil. + * @param cache The object that implements a cache of images for the image node. Weak reference. + * @param downloader The object that implements image downloading for the image node. Must not be nil. Weak reference. * * @discussion If `cache` is nil, the receiver will not attempt to retrieve images from a cache before downloading them. * diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 3d2fad8906..aa8cb483c4 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -16,8 +16,8 @@ @interface ASNetworkImageNode () { ASDN::RecursiveMutex _lock; - id _cache; - id _downloader; + __weak id _cache; + __weak id _downloader; // Only access any of these with _lock. __weak id _delegate; @@ -51,7 +51,7 @@ - (instancetype)init { - return [self initWithCache:nil downloader:[[ASBasicImageDownloader alloc] init]]; + return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; } - (void)dealloc diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index aa28d40a59..19100cfd7f 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -215,18 +215,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; -/** - * Relayouts the specified row using a given animation effect. - * - * @param indexPath The index path identifying the row to relayout. - * - * @param animation A constant that indicates how the relayout is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The relayout is excuted on main thread. - * The node of the specified row must be updated to cause layout changes before this method is called. - */ -- (void)relayoutRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation; - /** * Moves the row at a specified location to a destination location. * @@ -255,7 +243,7 @@ NS_ASSUME_NONNULL_BEGIN * * @returns an indexPath for this cellNode */ -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; +- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; /** * Similar to -visibleCells. diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 6729242e9c..e8be092d97 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -14,7 +14,6 @@ #import "ASCollectionViewLayoutController.h" #import "ASLayoutController.h" #import "ASRangeController.h" -#import "ASDisplayNodeInternal.h" #import "ASBatchFetching.h" #import "ASInternalHelpers.h" #import "ASLayout.h" @@ -152,7 +151,7 @@ static BOOL _isInterceptedSelector(SEL sel) #pragma mark - #pragma mark ASTableView -@interface ASTableView () { +@interface ASTableView () { _ASTableViewProxy *_proxyDataSource; _ASTableViewProxy *_proxyDelegate; @@ -180,26 +179,6 @@ static BOOL _isInterceptedSelector(SEL sel) @implementation ASTableView -/** - @summary Conditionally performs UIView geometry changes in the given block without animation. - - Used primarily to circumvent UITableView forcing insertion animations when explicitly told not to via - `UITableViewRowAnimationNone`. More info: https://github.com/facebook/AsyncDisplayKit/pull/445 - - @param withoutAnimation Set to `YES` to perform given block without animation - @param block Perform UIView geometry changes within the passed block - */ -void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - if (withoutAnimation) { - BOOL animationsEnabled = [UIView areAnimationsEnabled]; - [UIView setAnimationsEnabled:NO]; - block(); - [UIView setAnimationsEnabled:animationsEnabled]; - } else { - block(); - } -} - + (Class)dataControllerClass { return [ASChangeSetDataController class]; @@ -334,7 +313,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - (void)reloadDataWithCompletion:(void (^)())completion { ASDisplayNodeAssert(self.asyncDelegate, @"ASTableView's asyncDelegate property must be set."); - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [super reloadData]; }); [_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion]; @@ -477,14 +456,6 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; } -- (void)relayoutRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - ASCellNode *node = [self nodeForRowAtIndexPath:indexPath]; - [node setNeedsLayout]; - [super reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:animation]; -} - - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); @@ -849,6 +820,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { { ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); + node.layoutDelegate = self; return node; } @@ -923,4 +895,15 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { } } +#pragma mark - ASCellNodeDelegate + +- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation +{ + ASDisplayNodeAssertMainThread(); + NSIndexPath *indexPath = [self indexPathForNode:node]; + if (indexPath != nil) { + [super reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:(UITableViewRowAnimation)animation]; + } +} + @end diff --git a/AsyncDisplayKit/ASTextNode.h b/AsyncDisplayKit/ASTextNode.h index 72b28cf24d..52eef7ba92 100644 --- a/AsyncDisplayKit/ASTextNode.h +++ b/AsyncDisplayKit/ASTextNode.h @@ -81,6 +81,15 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { #pragma mark - Placeholders +/** + * @abstract ASTextNode has a special placeholder behavior when placeholderEnabled is YES. + * + * @discussion Defaults to NO. When YES, it draws rectangles for each line of text, + * following the true shape of the text's wrapping. This visually mirrors the overall + * shape and weight of paragraphs, making the appearance of the finished text less jarring. + */ +@property (nonatomic, assign) BOOL placeholderEnabled; + /** @abstract The placeholder color. */ diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index a88920892e..0f78ac960d 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -96,6 +96,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation UILongPressGestureRecognizer *_longPressGestureRecognizer; } +@dynamic placeholderEnabled; #pragma mark - NSObject @@ -131,7 +132,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); // Placeholders - self.placeholderEnabled = YES; + // Disabled by default in ASDisplayNode, but add a few options for those who toggle + // on the special placeholder behavior of ASTextNode. _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); _placeholderInsets = UIEdgeInsetsMake(1.0, 0.0, 1.0, 0.0); } @@ -747,6 +749,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (UIImage *)placeholderImage { + // FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set. + // This would completely eliminate the memory and performance cost of the backing store. CGSize size = self.calculatedSize; UIGraphicsBeginImageContext(size); [self.placeholderColor setFill]; diff --git a/AsyncDisplayKit/ASViewController.h b/AsyncDisplayKit/ASViewController.h index 98643b995d..118d594812 100644 --- a/AsyncDisplayKit/ASViewController.h +++ b/AsyncDisplayKit/ASViewController.h @@ -22,6 +22,15 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithNode:(ASDisplayNode *)node; +/** + * The constrained size used to measure the backing node. + * + * @discussion Defaults to providing a size range that uses the view controller view's bounds as + * both the min and max definitions. Override this method to provide a custom size range to the + * backing node. + */ +- (ASSizeRange)nodeConstrainedSize; + @end NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 15fe664323..3509ccbe85 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -40,9 +40,7 @@ - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; - CGSize viewSize = self.view.bounds.size; - ASSizeRange constrainedSize = ASSizeRangeMake(viewSize, viewSize); - [_node measureWithSizeRange:constrainedSize]; + [_node measureWithSizeRange:[self nodeConstrainedSize]]; } - (void)viewDidLayoutSubviews @@ -61,4 +59,12 @@ [_node recursivelyFetchData]; } +// MARK: - Layout Helpers + +- (ASSizeRange)nodeConstrainedSize +{ + CGSize viewSize = self.view.bounds.size; + return ASSizeRangeMake(viewSize, viewSize); +} + @end diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 2f4d77d15b..407d08b037 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -12,6 +12,7 @@ #import #import #import +#import #import diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.h b/AsyncDisplayKit/Details/ASBasicImageDownloader.h index 765a08e94b..a3a1b418ed 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.h +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.h @@ -15,6 +15,8 @@ NS_ASSUME_NONNULL_BEGIN */ @interface ASBasicImageDownloader : NSObject ++ (instancetype)sharedImageDownloader; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm index b1693c3017..ad1026d013 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm @@ -200,6 +200,16 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext @implementation ASBasicImageDownloader ++ (instancetype)sharedImageDownloader +{ + static ASBasicImageDownloader *sharedImageDownloader = nil; + static dispatch_once_t once = 0; + dispatch_once(&once, ^{ + sharedImageDownloader = [[ASBasicImageDownloader alloc] init]; + }); + return sharedImageDownloader; +} + #pragma mark Lifecycle. - (instancetype)init diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 7d7e81568c..e9a728c42e 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -14,7 +14,7 @@ #import "ASCellNode.h" #import "ASDisplayNode.h" #import "ASMultidimensionalArrayUtils.h" -#import "ASDisplayNodeInternal.h" +#import "ASInternalHelpers.h" #import "ASLayout.h" //#define LOG(...) NSLog(__VA_ARGS__) @@ -208,7 +208,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes); - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _completedNodes[kind] = completedNodes; if (completionBlock) { completionBlock(nodes, indexPaths); @@ -227,7 +227,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); if (completionBlock) { @@ -250,7 +250,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections); - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; if (completionBlock) { completionBlock(sections, indexSet); @@ -263,7 +263,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (indexSet.count == 0) return; [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; if (completionBlock) { completionBlock(indexSet); @@ -512,7 +512,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; LOG(@"endUpdatesWithCompletion - beginning"); [_editingTransactionQueue addOperationWithBlock:^{ - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ // Deep copy _completedNodes to _externalCompletedNodes. // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); @@ -532,7 +532,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_pendingEditCommandBlocks removeAllObjects]; [_editingTransactionQueue addOperationWithBlock:^{ - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ // Now that the transaction is done, _completedNodes can be accessed externally again. _externalCompletedNodes = nil; @@ -819,7 +819,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes // (see _layoutNodes:atIndexPaths:withAnimationOptions:). [_editingTransactionQueue addOperationWithBlock:^{ - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ for (NSString *kind in [_completedNodes keyEnumerator]) { [self _relayoutNodesOfKind:kind]; } diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 146b85897f..b511c4d43f 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -10,10 +10,10 @@ #import "ASAssert.h" #import "ASDisplayNodeExtras.h" -#import "ASDisplayNodeInternal.h" #import "ASMultiDimensionalArrayUtils.h" #import "ASRangeHandlerRender.h" #import "ASRangeHandlerPreload.h" +#import "ASInternalHelpers.h" @interface ASRangeController () { BOOL _rangeIsValid; @@ -177,13 +177,13 @@ #pragma mark - ASDataControllerDelegete - (void)dataControllerBeginUpdates:(ASDataController *)dataController { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [_delegate rangeControllerBeginUpdates:self]; }); } - (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [_delegate rangeController:self endUpdatesAnimated:animated completion:completion]; }); } @@ -196,14 +196,14 @@ [nodeSizes addObject:[NSValue valueWithCGSize:node.calculatedSize]]; }]; - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }); } - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }); @@ -222,14 +222,14 @@ [sectionNodeSizes addObject:nodeSizes]; }]; - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h index d6aa54b157..6207e06fad 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h @@ -58,6 +58,16 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray> *)children; +/** + * @return A stack layout spec with direction of ASStackLayoutDirectionVertical + **/ ++ (instancetype)verticalStackLayoutSpec; + +/** + * @return A stack layout spec with direction of ASStackLayoutDirectionHorizontal + **/ ++ (instancetype)horizontalStackLayoutSpec; + @end NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 2269ffffeb..0763bf2615 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -38,6 +38,20 @@ return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems children:children]; } ++ (instancetype)verticalStackLayoutSpec +{ + ASStackLayoutSpec *stackLayoutSpec = [[self alloc] init]; + stackLayoutSpec.direction = ASStackLayoutDirectionVertical; + return stackLayoutSpec; +} + ++ (instancetype)horizontalStackLayoutSpec +{ + ASStackLayoutSpec *stackLayoutSpec = [[self alloc] init]; + stackLayoutSpec.direction = ASStackLayoutDirectionHorizontal; + return stackLayoutSpec; +} + - (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children { if (!(self = [super init])) { diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 63ef4ed8da..652c1fa890 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -85,7 +85,7 @@ return _getFromLayer(cornerRadius); } --(void)setCornerRadius:(CGFloat)newCornerRadius +- (void)setCornerRadius:(CGFloat)newCornerRadius { _bridge_prologue; _setToLayer(cornerRadius, newCornerRadius); diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index c2daf155be..602da5aa17 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -20,7 +20,6 @@ #import "ASLayoutOptions.h" BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); -void ASDisplayNodePerformBlockOnMainThread(void (^block)()); void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)()); typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index 00bed4b1d6..4d1e2ddca8 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -10,12 +10,14 @@ #include #import +#import #import "ASBaseDefines.h" ASDISPLAYNODE_EXTERN_C_BEGIN BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector); BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL selector); +void ASPerformBlockOnMainThread(void (^block)()); CGFloat ASScreenScale(); @@ -27,6 +29,23 @@ CGFloat ASRoundPixelValue(CGFloat f); ASDISPLAYNODE_EXTERN_C_END +/** + @summary Conditionally performs UIView geometry changes in the given block without animation. + + Used primarily to circumvent UITableView forcing insertion animations when explicitly told not to via + `UITableViewRowAnimationNone`. More info: https://github.com/facebook/AsyncDisplayKit/pull/445 + + @param withoutAnimation Set to `YES` to perform given block without animation + @param block Perform UIView geometry changes within the passed block + */ +ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { + if (withoutAnimation) { + [UIView performWithoutAnimation:block]; + } else { + block(); + } +} + @interface NSIndexPath (ASInverseComparison) - (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath; @end diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.mm b/AsyncDisplayKit/Private/ASInternalHelpers.mm index d2337a44b8..89a0e81709 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.mm +++ b/AsyncDisplayKit/Private/ASInternalHelpers.mm @@ -46,6 +46,17 @@ static void ASDispatchOnceOnMainThread(dispatch_once_t *predicate, dispatch_bloc } } +void ASPerformBlockOnMainThread(void (^block)()) +{ + if ([NSThread isMainThread]) { + block(); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + block(); + }); + } +} + CGFloat ASScreenScale() { static CGFloat _scale; diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index f6035e03be..847a2629c7 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -185,22 +185,13 @@ static const CGFloat kInnerPadding = 10.0f; - (void)toggleImageEnlargement { _isImageEnlarged = !_isImageEnlarged; + [self setNeedsLayout]; } - (void)toggleNodesSwap { _swappedTextAndImage = !_swappedTextAndImage; - - [UIView animateWithDuration:0.15 animations:^{ - self.alpha = 0; - } completion:^(BOOL finished) { - [self setNeedsLayout]; - [self.view layoutIfNeeded]; - - [UIView animateWithDuration:0.15 animations:^{ - self.alpha = 1; - }]; - }]; + [self setNeedsLayout]; } @end diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 41630435aa..63df8deed9 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -121,7 +121,6 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell // Assume only kitten nodes are selectable (see -tableView:shouldHighlightRowAtIndexPath:). KittenNode *node = (KittenNode *)[_tableView nodeForRowAtIndexPath:indexPath]; [node toggleImageEnlargement]; - [_tableView relayoutRowAtIndexPath:indexPath withRowAnimation:UITableViewRowAnimationAutomatic]; } - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath @@ -166,8 +165,6 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell - (void)tableView:(UITableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context { - NSLog(@"adding kitties"); - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); dispatch_async(dispatch_get_main_queue(), ^{ @@ -189,8 +186,6 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; [context completeBatchFetching:YES]; - - NSLog(@"kittens added"); }); }); } diff --git a/examples/Multiplex/Sample.xcodeproj/project.pbxproj b/examples/Multiplex/Sample.xcodeproj/project.pbxproj index 1383d38552..7408b333ba 100644 --- a/examples/Multiplex/Sample.xcodeproj/project.pbxproj +++ b/examples/Multiplex/Sample.xcodeproj/project.pbxproj @@ -123,6 +123,7 @@ 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + 93B7780A33739EF25F20366B /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -179,6 +180,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 93B7780A33739EF25F20366B /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/examples/Multiplex/Sample/ScreenNode.h b/examples/Multiplex/Sample/ScreenNode.h index b1b5024ed6..9a818befa0 100644 --- a/examples/Multiplex/Sample/ScreenNode.h +++ b/examples/Multiplex/Sample/ScreenNode.h @@ -11,7 +11,7 @@ @interface ScreenNode : ASDisplayNode @property (nonatomic, strong) ASMultiplexImageNode *imageNode; -@property (nonatomic, strong) ASTextNode *textNode; +@property (nonatomic, strong) ASButtonNode *buttonNode; - (void)start; - (void)reload; diff --git a/examples/Multiplex/Sample/ScreenNode.m b/examples/Multiplex/Sample/ScreenNode.m index 632a50c4fb..b3e661079a 100644 --- a/examples/Multiplex/Sample/ScreenNode.m +++ b/examples/Multiplex/Sample/ScreenNode.m @@ -31,11 +31,13 @@ // load low-quality images before high-quality images _imageNode.downloadsIntermediateImages = YES; - // simple status label - _textNode = [[ASTextNode alloc] init]; + // simple status label. Synchronous to avoid flicker / placeholder state when updating. + _buttonNode = [[ASButtonNode alloc] init]; + [_buttonNode addTarget:self action:@selector(reload) forControlEvents:ASControlNodeEventTouchUpInside]; + _buttonNode.titleNode.displaysAsynchronously = NO; [self addSubnode:_imageNode]; - [self addSubnode:_textNode]; + [self addSubnode:_buttonNode]; return self; } @@ -43,11 +45,12 @@ - (void)start { [self setText:@"loading…"]; - _textNode.userInteractionEnabled = NO; + _buttonNode.userInteractionEnabled = NO; _imageNode.imageIdentifiers = @[ @"best", @"medium", @"worst" ]; // go! } -- (void)reload { +- (void)reload +{ [self start]; [_imageNode reloadImageIdentifierSources]; } @@ -57,18 +60,21 @@ NSDictionary *attributes = @{NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:22.0f]}; NSAttributedString *string = [[NSAttributedString alloc] initWithString:text attributes:attributes]; - _textNode.attributedString = string; + [_buttonNode setAttributedTitle:string forState:ASButtonStateNormal]; [self setNeedsLayout]; } - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { ASRatioLayoutSpec *imagePlaceholder = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1 child:_imageNode]; - ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical - spacing:10 - justifyContent:ASStackLayoutJustifyContentCenter - alignItems:ASStackLayoutAlignItemsCenter - children:@[imagePlaceholder, _textNode]]; + + ASStackLayoutSpec *verticalStack = [[ASStackLayoutSpec alloc] init]; + verticalStack.direction = ASStackLayoutDirectionVertical; + verticalStack.spacing = 10; + verticalStack.justifyContent = ASStackLayoutJustifyContentCenter; + verticalStack.alignItems = ASStackLayoutAlignItemsCenter; + verticalStack.children = @[imagePlaceholder, _buttonNode]; + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) child:verticalStack]; } @@ -98,8 +104,8 @@ [self setText:[NSString stringWithFormat:@"loaded '%@'", imageIdentifier]]; if ([imageIdentifier isEqualToString:@"best"]) { - [self setText:[_textNode.attributedString.string stringByAppendingString:@". tap to reload"]]; - _textNode.userInteractionEnabled = YES; + [self setText:[_buttonNode.titleNode.attributedString.string stringByAppendingString:@". tap to reload"]]; + _buttonNode.userInteractionEnabled = YES; } } diff --git a/examples/Multiplex/Sample/ViewController.m b/examples/Multiplex/Sample/ViewController.m index e694aa79bd..2b5471d6f5 100644 --- a/examples/Multiplex/Sample/ViewController.m +++ b/examples/Multiplex/Sample/ViewController.m @@ -12,7 +12,8 @@ #import "ViewController.h" #import "ScreenNode.h" -@interface ViewController() { +@interface ViewController() +{ ScreenNode *_screenNode; } @@ -28,10 +29,6 @@ _screenNode = node; - // tap to reload - UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(reload:)]; - [_screenNode.textNode.view addGestureRecognizer:gr]; - return self; } @@ -42,10 +39,6 @@ [super viewWillAppear:animated]; } -- (void)reload:(id)sender { - [_screenNode reload]; -} - - (BOOL)prefersStatusBarHidden { return YES;