Merge branch 'master' into update-objc

Conflicts:
	AsyncDisplayKit/ASCollectionView.h
	AsyncDisplayKit/ASDisplayNode.h
	AsyncDisplayKit/Details/ASDataController.h
	AsyncDisplayKit/Details/UIView+ASConvenience.h
	AsyncDisplayKit/Layout/ASLayoutSpec.h
This commit is contained in:
Adlai Holler
2015-10-28 21:10:27 -07:00
84 changed files with 4314 additions and 353 deletions

2
.gitignore vendored
View File

@@ -26,3 +26,5 @@ docs/.sass-cache
*.lock
*.gcov
*.gcno
*.gcda

BIN
AsyncDisplayKit-Prefix.gcda Normal file

Binary file not shown.

View File

@@ -1,11 +1,11 @@
Pod::Spec.new do |spec|
spec.name = 'AsyncDisplayKit'
spec.version = '1.2.2'
spec.version = '1.9'
spec.license = { :type => 'BSD' }
spec.homepage = 'http://asyncdisplaykit.org'
spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' }
spec.summary = 'Smooth asynchronous user interfaces for iOS apps.'
spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.2.2' }
spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9' }
spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/'

View File

@@ -140,6 +140,12 @@
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 = (); }; };
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 = (); }; };
2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; };
@@ -206,8 +212,14 @@
509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; };
509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; };
6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; };
9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; };
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 = (); }; };
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, ); }; };
9C5FA3531B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C5FA3501B8F6ADF00A62714 /* ASLayoutOptions.mm */; };
@@ -225,6 +237,15 @@
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 */; };
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 */; };
AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */; };
AC026B6F1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */; };
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 */; };
@@ -233,6 +254,8 @@
AC47D9451B3BB41900AAEE9D /* ASRelativeSize.h in Headers */ = {isa = PBXBuildFile; fileRef = AC47D9431B3BB41900AAEE9D /* ASRelativeSize.h */; settings = {ATTRIBUTES = (Public, ); }; };
AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC47D9441B3BB41900AAEE9D /* ASRelativeSize.mm */; };
AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = AC6456071B0A335000CF11B8 /* ASCellNode.m */; };
AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */; };
AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */; };
ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
ACC945AB1BA9E7C1005E1FB8 /* ASViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */; };
ACF6ED1A1B17843500DA7C62 /* ASBackgroundLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -544,6 +567,12 @@
205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = "<group>"; };
205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = "<group>"; };
242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = "<group>"; };
251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionDataController.h; sourceTree = "<group>"; };
251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionDataController.mm; sourceTree = "<group>"; };
251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = "<group>"; };
251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = "<group>"; };
251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDataController+Subclasses.h"; sourceTree = "<group>"; };
2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspectorTests.m; sourceTree = "<group>"; };
2911485B1A77147A005D0878 /* ASControlNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNodeTests.m; sourceTree = "<group>"; };
292C59991A956527007E5DD6 /* ASLayoutRangeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutRangeType.h; sourceTree = "<group>"; };
292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerPreload.h; sourceTree = "<group>"; };
@@ -567,6 +596,8 @@
4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = "<group>"; };
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = "<group>"; };
9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; sourceTree = "<group>"; };
9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASAsciiArtBoxCreator.h; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.h; sourceTree = "<group>"; };
9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASAsciiArtBoxCreator.m; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m; sourceTree = "<group>"; };
9C5FA34F1B8F6ADF00A62714 /* ASLayoutOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutOptions.h; path = AsyncDisplayKit/Layout/ASLayoutOptions.h; sourceTree = "<group>"; };
9C5FA3501B8F6ADF00A62714 /* ASLayoutOptions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASLayoutOptions.mm; path = AsyncDisplayKit/Layout/ASLayoutOptions.mm; sourceTree = "<group>"; };
9C5FA35C1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASLayoutOptionsPrivate.mm; path = AsyncDisplayKit/Layout/ASLayoutOptionsPrivate.mm; sourceTree = "<group>"; };
@@ -576,6 +607,11 @@
9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackBaselinePositionedLayout.mm; sourceTree = "<group>"; };
9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutablePrivate.h; path = AsyncDisplayKit/Layout/ASLayoutablePrivate.h; sourceTree = "<group>"; };
9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewTests.m; sourceTree = "<group>"; };
AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASChangeSetDataController.h; sourceTree = "<group>"; };
AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASChangeSetDataController.m; sourceTree = "<group>"; };
AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASHierarchyChangeSet.h; sourceTree = "<group>"; };
AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASHierarchyChangeSet.m; sourceTree = "<group>"; };
AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASStaticLayoutSpecSnapshotTests.m; sourceTree = "<group>"; };
AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutDefines.h; path = AsyncDisplayKit/Layout/ASStackLayoutDefines.h; sourceTree = "<group>"; };
AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionView.h; sourceTree = "<group>"; };
AC3C4A501A1139C100143C57 /* ASCollectionView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionView.mm; sourceTree = "<group>"; };
@@ -583,6 +619,7 @@
AC47D9431B3BB41900AAEE9D /* ASRelativeSize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRelativeSize.h; path = AsyncDisplayKit/Layout/ASRelativeSize.h; sourceTree = "<group>"; };
AC47D9441B3BB41900AAEE9D /* ASRelativeSize.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRelativeSize.mm; path = AsyncDisplayKit/Layout/ASRelativeSize.mm; sourceTree = "<group>"; };
AC6456071B0A335000CF11B8 /* ASCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCellNode.m; sourceTree = "<group>"; };
AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableViewInternal.h; sourceTree = "<group>"; };
ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASViewController.h; sourceTree = "<group>"; };
ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASViewController.m; sourceTree = "<group>"; };
ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASBackgroundLayoutSpec.h; path = AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h; sourceTree = "<group>"; };
@@ -769,6 +806,7 @@
D785F6611A74327E00291744 /* ASScrollNode.m */,
055F1A3219ABD3E3004DAFF1 /* ASTableView.h */,
055F1A3319ABD3E3004DAFF1 /* ASTableView.mm */,
AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */,
0574D5E119C110610097DC25 /* ASTableViewProtocols.h */,
058D09DF195D050800B7D73C /* ASTextNode.h */,
058D09E0195D050800B7D73C /* ASTextNode.mm */,
@@ -805,6 +843,7 @@
ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */,
ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */,
ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */,
AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */,
ACF6ED571B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.h */,
ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */,
242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */,
@@ -829,6 +868,7 @@
058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */,
058D09C6195D04C000B7D73C /* Supporting Files */,
052EE06A1A15A0D8002C6279 /* TestResources */,
2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */,
);
path = AsyncDisplayKitTests;
sourceTree = "<group>";
@@ -847,6 +887,11 @@
children = (
CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */,
CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */,
251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */,
251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */,
251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */,
251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */,
251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */,
058D09E2195D050800B7D73C /* _ASDisplayLayer.h */,
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */,
058D09E4195D050800B7D73C /* _ASDisplayView.h */,
@@ -861,6 +906,8 @@
205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */,
464052191A3F83C40061C0BA /* ASDataController.h */,
4640521A1A3F83C40061C0BA /* ASDataController.mm */,
AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */,
AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */,
05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */,
05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */,
4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */,
@@ -925,6 +972,8 @@
058D0A01195D050800B7D73C /* Private */ = {
isa = PBXGroup;
children = (
AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */,
AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */,
9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */,
9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */,
9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */,
@@ -976,6 +1025,8 @@
AC6456051B0A333200CF11B8 /* Layout */ = {
isa = PBXGroup;
children = (
9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */,
9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */,
ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */,
ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */,
ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */,
@@ -1044,6 +1095,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */,
058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */,
058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */,
058D0A6A195D05EC00B7D73C /* _ASAsyncTransactionContainer+Private.h in Headers */,
@@ -1053,6 +1105,7 @@
058D0A53195D05DC00B7D73C /* _ASDisplayLayer.h in Headers */,
058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */,
058D0A74195D05F800B7D73C /* _ASPendingState.h in Headers */,
9C5586691BD549CB00B50E3A /* ASAsciiArtBoxCreator.h in Headers */,
058D0A76195D05F900B7D73C /* _ASScopeTimer.h in Headers */,
205F0E191B37339C007741D0 /* ASAbstractLayoutController.h in Headers */,
058D0A82195D060300B7D73C /* ASAssert.h in Headers */,
@@ -1062,6 +1115,7 @@
054963491A1EA066000F8E56 /* ASBasicImageDownloader.h in Headers */,
2967F9E21AB0A5190072E4AB /* ASBasicImageDownloaderInternal.h in Headers */,
299DA1A91A828D2900162D41 /* ASBatchContext.h in Headers */,
251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */,
044285071BAA63FE00D16268 /* ASBatchFetching.h in Headers */,
055F1A3C19ABD43F004DAFF1 /* ASCellNode.h in Headers */,
ACF6ED1C1B17843500DA7C62 /* ASCenterLayoutSpec.h in Headers */,
@@ -1078,6 +1132,7 @@
058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */,
058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */,
058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */,
AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */,
058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */,
058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */,
0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */,
@@ -1091,6 +1146,7 @@
ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */,
ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */,
ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */,
251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */,
ACF6ED2A1B17843500DA7C62 /* ASLayoutable.h in Headers */,
9CDC18CC1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */,
464052241A3F83C40061C0BA /* ASLayoutController.h in Headers */,
@@ -1099,6 +1155,7 @@
292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */,
ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */,
ACF6ED4D1B17847A00DA7C62 /* ASLayoutSpecUtilities.h in Headers */,
AC026B6F1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */,
0516FA3D1A15563400B4EBED /* ASLog.h in Headers */,
0442850D1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */,
0516FA401A1563D200B4EBED /* ASMultiplexImageNode.h in Headers */,
@@ -1125,6 +1182,7 @@
9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */,
ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */,
055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */,
251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */,
0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */,
058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */,
058D0A5B195D05DC00B7D73C /* ASTextNodeCoreTextAdditions.h in Headers */,
@@ -1147,6 +1205,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */,
B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */,
B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */,
B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */,
@@ -1156,6 +1215,7 @@
B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */,
B35062111B010EFD0018CF92 /* _ASDisplayView.h in Headers */,
B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */,
9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */,
B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */,
509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */,
B35062571B010F070018CF92 /* ASAssert.h in Headers */,
@@ -1166,6 +1226,7 @@
B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */,
B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */,
044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */,
AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */,
B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */,
34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */,
18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
@@ -1187,6 +1248,7 @@
B350625B1B010F070018CF92 /* ASEqualityHelpers.h in Headers */,
B350621B1B010EFD0018CF92 /* ASFlowLayoutController.h in Headers */,
B350621D1B010EFD0018CF92 /* ASHighlightOverlayLayer.h in Headers */,
AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */,
B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */,
B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */,
B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */,
@@ -1442,8 +1504,10 @@
058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */,
058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */,
058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */,
AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */,
058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */,
058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */,
9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */,
058D0A27195D050800B7D73C /* _ASPendingState.m in Sources */,
205F0E1A1B37339C007741D0 /* ASAbstractLayoutController.mm in Sources */,
ACF6ED1B1B17843500DA7C62 /* ASBackgroundLayoutSpec.mm in Sources */,
@@ -1474,6 +1538,7 @@
ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */,
9C5FA3531B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */,
9C5FA35F1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */,
251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */,
ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */,
0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */,
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */,
@@ -1490,10 +1555,12 @@
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */,
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */,
ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */,
ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */,
ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */,
ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */,
AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */,
055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */,
058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */,
058D0A1C195D050800B7D73C /* ASTextNodeCoreTextAdditions.m in Sources */,
@@ -1505,7 +1572,6 @@
205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */,
058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */,
058D0A25195D050800B7D73C /* UIView+ASConvenience.m in Sources */,
CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1522,10 +1588,12 @@
2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */,
ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */,
058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */,
2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */,
058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */,
058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */,
058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */,
056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */,
AC026B581BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m in Sources */,
ACF6ED5E1B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm in Sources */,
ACF6ED601B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m in Sources */,
CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */,
@@ -1548,12 +1616,16 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */,
9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */,
B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.m in Sources */,
B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */,
AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */,
B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */,
B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */,
2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */,
B35062101B010EFD0018CF92 /* _ASDisplayLayer.mm in Sources */,
9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.m in Sources */,
B35062121B010EFD0018CF92 /* _ASDisplayView.mm in Sources */,
B350624C1B010EFD0018CF92 /* _ASPendingState.m in Sources */,
509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */,
@@ -1604,6 +1676,7 @@
34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */,
34EFC7761B701D2A00AD841F /* ASStackPositionedLayout.mm in Sources */,
34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */,
AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */,
34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */,
B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */,
B350620E1B010EFD0018CF92 /* ASTextNode.mm in Sources */,
@@ -1616,7 +1689,6 @@
34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */,
B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */,
B35062441B010EFD0018CF92 /* UIView+ASConvenience.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -44,6 +44,16 @@ NS_ASSUME_NONNULL_BEGIN
//@property (atomic, retain) UIColor *backgroundColor;
@property (nonatomic) UITableViewCellSelectionStyle selectionStyle;
/*
* A Boolean value that indicates whether the node is selected.
*/
@property (nonatomic, assign) BOOL selected;
/*
* A Boolean value that indicates whether the node is highlighted.
*/
@property (nonatomic, assign) BOOL highlighted;
/*
* 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.

View File

@@ -123,7 +123,6 @@ static const CGFloat kFontSize = 18.0f;
_textNode.attributedString = [[NSAttributedString alloc] initWithString:_text
attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kFontSize]}];
[self invalidateCalculatedLayout];
}
@end

View File

@@ -17,6 +17,7 @@
@class ASCellNode;
@protocol ASCollectionViewDataSource;
@protocol ASCollectionViewDelegate;
@protocol ASCollectionViewLayoutInspecting;
NS_ASSUME_NONNULL_BEGIN
@@ -64,7 +65,7 @@ NS_ASSUME_NONNULL_BEGIN
*
* @param asyncDataFetchingEnabled Enable the data fetching in async mode.
*
* @discussion If asyncDataFetching is enabled, the `AScollectionView` will fetch data through `collectionView:numberOfRowsInSection:` and
* @discussion If asyncDataFetching is enabled, the `ASCollectionView` will fetch data through `collectionView:numberOfRowsInSection:` and
* `collectionView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically
* from calling thread.
* Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for
@@ -81,6 +82,18 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
/**
* Optional introspection object for the collection view's layout.
*
* @discussion Since supplementary and decoration views are controlled by the collection view's layout, this object
* is used as a bridge to provide information to the internal data controller about the existence of these views and
* their associated index paths. For collection views using `UICollectionViewFlowLayout`, a default inspector
* implementation `ASCollectionViewFlowLayoutInspector` is created and set on this property by default. Custom
* collection view layout subclasses will need to provide their own implementation of an inspector object for their
* supplementary views to be compatible with `ASCollectionView`'s supplementary node support.
*/
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector;
/**
* Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread.
* The asyncDataSource must be updated to reflect the changes before the update block completes.
@@ -120,6 +133,26 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)reloadData;
/**
* Reload everything from scratch entirely on the main thread, destroying the working range and all cached nodes.
*
* @warning This method is substantially more expensive than UICollectionView's version and will block the main thread
* while all the cells load.
*/
- (void)reloadDataImmediately;
/**
* Registers the given kind of supplementary node for use in creating node-backed supplementary views.
*
* @param kind The kind of supplementary node that will be requested through the data source.
*
* @discussion Use this method to register support for the use of supplementary nodes in place of the default
* `registerClass:forSupplementaryViewOfKind:withReuseIdentifier:` and `registerNib:forSupplementaryViewOfKind:withReuseIdentifier:`
* methods. This method will register an internal backing view that will host the contents of the supplementary nodes
* returned from the data source.
*/
- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind;
/**
* Inserts one or more sections.
*
@@ -192,6 +225,16 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)reloadItemsAtIndexPaths:(NSArray<NSIndexPath *> *)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.
*
@@ -273,6 +316,15 @@ NS_ASSUME_NONNULL_BEGIN
@optional
/**
* Asks the collection view to provide a supplementary node to display in the collection view.
*
* @param collectionView An object representing the collection view requesting this information.
* @param kind The kind of supplementary node to provide.
* @param indexPath The index path that specifies the location of the new supplementary node.
*/
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
* Provides the constrained size range for measuring the node at the index path.
*
@@ -343,18 +395,37 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView;
@end
/**
* Defines methods that let you coordinate with a `UICollectionViewFlowLayout` in combination with an `ASCollectionView`.
*/
@protocol ASCollectionViewDelegateFlowLayout <ASCollectionViewDelegate>
@optional
/**
* Passthrough support to UICollectionViewDelegateFlowLayout sectionInset behavior.
*
* @param collectionView The sender.
* @param collectionViewLayout The layout object requesting the information.
* #param section The index number of the section whose insets are needed.
* @param section The index number of the section whose insets are needed.
*
* @discussion The same rules apply as the UICollectionView implementation, but this can also be used without a UICollectionViewFlowLayout.
* https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewDelegateFlowLayout_protocol/index.html#//apple_ref/occ/intfm/UICollectionViewDelegateFlowLayout/collectionView:layout:insetForSectionAtIndex:
*
*/
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
/**
* Asks the delegate for the size of the header in the specified section.
*/
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
/**
* Asks the delegate for the size of the footer in the specified section.
*/
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
@end

View File

@@ -11,11 +11,12 @@
#import "ASAssert.h"
#import "ASCollectionViewLayoutController.h"
#import "ASRangeController.h"
#import "ASDataController.h"
#import "ASCollectionDataController.h"
#import "ASDisplayNodeInternal.h"
#import "ASBatchFetching.h"
#import "UICollectionViewLayout+ASConvenience.h"
#import "ASInternalHelpers.h"
#import "ASCollectionViewFlowLayoutInspector.h"
// FIXME: Temporary nonsense import until method names are finalized and exposed
#import "ASDisplayNode+Subclasses.h"
@@ -37,9 +38,7 @@ static BOOL _isInterceptedSelector(SEL sel)
// handled by ASCollectionView node<->cell machinery
sel == @selector(collectionView:cellForItemAtIndexPath:) ||
sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
// TODO: Supplementary views are currently not supported. An assertion is triggered if the _asyncDataSource implements this method.
// sel == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) ||
sel == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) ||
// handled by ASRangeController
sel == @selector(numberOfSectionsInCollectionView:) ||
@@ -106,6 +105,28 @@ static BOOL _isInterceptedSelector(SEL sel)
@end
#pragma mark -
#pragma mark ASCellNode<->UICollectionViewCell bridging.
@class _ASCollectionViewCell;
@interface _ASCollectionViewCell : UICollectionViewCell
@property (nonatomic, weak) ASCellNode *node;
@end
@implementation _ASCollectionViewCell
- (void)setSelected:(BOOL)selected
{
_node.selected = selected;
}
- (void)setHighlighted:(BOOL)highlighted
{
_node.highlighted = highlighted;
}
@end
#pragma mark -
#pragma mark ASCollectionView.
@@ -114,9 +135,10 @@ static BOOL _isInterceptedSelector(SEL sel)
_ASCollectionViewProxy *_proxyDataSource;
_ASCollectionViewProxy *_proxyDelegate;
ASDataController *_dataController;
ASCollectionDataController *_dataController;
ASRangeController *_rangeController;
ASCollectionViewLayoutController *_layoutController;
ASCollectionViewFlowLayoutInspector *_flowLayoutInspector;
BOOL _performingBatchUpdates;
NSMutableArray *_batchUpdateBlocks;
@@ -131,6 +153,8 @@ static BOOL _isInterceptedSelector(SEL sel)
CGSize _maxSizeForNodesConstrainedSize;
BOOL _ignoreMaxSizeChange;
NSMutableSet *_registeredSupplementaryKinds;
/**
* If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it.
@@ -179,10 +203,10 @@ static BOOL _isInterceptedSelector(SEL sel)
_rangeController.delegate = self;
_rangeController.layoutController = _layoutController;
_dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled];
_dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled];
_dataController.delegate = _rangeController;
_dataController.dataSource = self;
_batchContext = [[ASBatchContext alloc] init];
_leadingScreensForBatching = 1.0;
@@ -202,9 +226,17 @@ static BOOL _isInterceptedSelector(SEL sel)
// and should not trigger a relayout.
_ignoreMaxSizeChange = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero);
// Register the default layout inspector delegate for flow layouts only, custom layouts
// will need to roll their own ASCollectionViewLayoutInspecting implementation and set a layout delegate
if ([layout asdk_isFlowLayout]) {
_layoutInspector = [self flowLayoutInspector];
}
_registeredSupplementaryKinds = [NSMutableSet set];
self.backgroundColor = [UIColor whiteColor];
[self registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"_ASCollectionViewCell"];
[self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:@"_ASCollectionViewCell"];
return self;
}
@@ -217,6 +249,20 @@ static BOOL _isInterceptedSelector(SEL sel)
super.dataSource = nil;
}
/**
* A layout inspector implementation specific for the sizing behavior of UICollectionViewFlowLayouts
*/
- (ASCollectionViewFlowLayoutInspector *)flowLayoutInspector
{
if (_flowLayoutInspector == nil) {
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout;
ASDisplayNodeAssertNotNil(layout, @"Collection view layout must be a flow layout to use the built-in inspector");
_flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self
flowLayout:layout];
}
return _flowLayoutInspector;
}
#pragma mark -
#pragma mark Overrides.
@@ -235,6 +281,13 @@ static BOOL _isInterceptedSelector(SEL sel)
[self reloadDataWithCompletion:nil];
}
- (void)reloadDataImmediately
{
ASDisplayNodeAssertMainThread();
[_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone];
[super reloadData];
}
- (void)setDataSource:(id<UICollectionViewDataSource>)dataSource
{
// UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil.
@@ -261,10 +314,6 @@ static BOOL _isInterceptedSelector(SEL sel)
_asyncDataSourceImplementsConstrainedSizeForNode = NO;
} else {
_asyncDataSource = asyncDataSource;
// TODO: Support supplementary views with ASCollectionView.
if ([_asyncDataSource respondsToSelector:@selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:)]) {
ASDisplayNodeAssert(NO, @"ASCollectionView is planned to support supplementary views by September 2015. You can work around this issue by using standard items.");
}
_proxyDataSource = [[_ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
_asyncDataSourceImplementsConstrainedSizeForNode = ([_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] ? 1 : 0);
@@ -291,6 +340,8 @@ static BOOL _isInterceptedSelector(SEL sel)
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
_asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0);
}
[_layoutInspector didChangeCollectionViewDelegate:asyncDelegate];
}
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
@@ -357,6 +408,14 @@ static BOOL _isInterceptedSelector(SEL sel)
[self performBatchAnimated:YES updates:updates completion:completion];
}
- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind
{
ASDisplayNodeAssert(elementKind != nil, @"A kind is needed for supplementary node registration");
[_registeredSupplementaryKinds addObject:elementKind];
[self registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:elementKind
withReuseIdentifier:[self __reuseIdentifierForKind:elementKind]];
}
- (void)insertSections:(NSIndexSet *)sections
{
ASDisplayNodeAssertMainThread();
@@ -399,12 +458,25 @@ 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();
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone];
}
- (NSString *)__reuseIdentifierForKind:(NSString *)kind
{
return [@"_ASCollectionSupplementaryView_" stringByAppendingString:kind];
}
#pragma mark -
#pragma mark Intercepted selectors.
@@ -412,12 +484,14 @@ static BOOL _isInterceptedSelector(SEL sel)
{
static NSString *reuseIdentifier = @"_ASCollectionViewCell";
UICollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
_ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
[_rangeController configureContentView:cell.contentView forCellNode:node];
cell.node = node;
return cell;
}
@@ -426,6 +500,15 @@ static BOOL _isInterceptedSelector(SEL sel)
return [[_dataController nodeAtIndexPath:indexPath] calculatedSize];
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = [self __reuseIdentifierForKind:kind];
UICollectionReusableView *view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:identifier forIndexPath:indexPath];
ASCellNode *node = [_dataController supplementaryNodeOfKind:kind atIndexPath:indexPath];
[_rangeController configureContentView:view forCellNode:node];
return view;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
_superIsPendingDataLoad = NO;
@@ -525,7 +608,7 @@ static BOOL _isInterceptedSelector(SEL sel)
_ignoreMaxSizeChange = NO;
} else {
[self performBatchAnimated:NO updates:^{
[_dataController relayoutAllRows];
[_dataController relayoutAllNodes];
} completion:nil];
}
}
@@ -605,7 +688,7 @@ static BOOL _isInterceptedSelector(SEL sel)
}
if (_asyncDelegateImplementsInsetSection) {
sectionInset = [_asyncDelegate collectionView:self layout:self.collectionViewLayout insetForSectionAtIndex:indexPath.section];
sectionInset = [(id<ASCollectionViewDelegateFlowLayout>)_asyncDelegate collectionView:self layout:self.collectionViewLayout insetForSectionAtIndex:indexPath.section];
}
if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) {
@@ -630,7 +713,7 @@ static BOOL _isInterceptedSelector(SEL sel)
return [_asyncDataSource collectionView:self numberOfItemsInSection:section];
}
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController {
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController {
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
return [_asyncDataSource numberOfSectionsInCollectionView:self];
} else {
@@ -658,8 +741,39 @@ static BOOL _isInterceptedSelector(SEL sel)
}
}
#pragma mark -
#pragma mark ASRangeControllerDelegate.
#pragma mark - ASCollectionViewDataControllerSource Supplementary view support
- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
ASCellNode *node = [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath];
ASDisplayNodeAssert(node != nil, @"A node must be returned for a supplementary node");
return node;
}
- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController
{
return [_registeredSupplementaryKinds allObjects];
}
- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)");
return [_layoutInspector collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath];
}
- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section
{
ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)");
return [_layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section];
}
- (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind;
{
ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)");
return [_layoutInspector collectionView:self numberOfSectionsForSupplementaryNodeOfKind:kind];
}
#pragma mark - ASRangeControllerDelegate.
- (void)rangeControllerBeginUpdates:(ASRangeController *)rangeController {
ASDisplayNodeAssertMainThread();

View File

@@ -12,7 +12,7 @@
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASDealloc2MainObject.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASAsciiArtBoxCreator.h>
#import <AsyncDisplayKit/ASLayoutable.h>
NS_ASSUME_NONNULL_BEGIN
@@ -110,6 +110,10 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
/** @name Properties */
/**
* @abstract The name of this node, which will be displayed in `description`. The default value is nil.
*/
@property (atomic, copy) NSString *name;
/**
* @abstract Returns whether the node is synchronous.
@@ -521,7 +525,7 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
* Convenience methods for debugging.
*/
@interface ASDisplayNode (Debugging)
@interface ASDisplayNode (Debugging) <ASLayoutableAsciiArtProtocol>
/**
* @abstract Return a description of the node hierarchy.
@@ -541,7 +545,7 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
* Using them will not cause the actual view/layer to be created, and will be applied when it is created (when the view
* or layer property is accessed).
*
* After the view is created, the properties pass through to the view directly as if called on the main thread.
* - NOTE: After the view or layer is created, the properties pass through to the view or layer directly and must be called on the main thread.
*
* See UIView and CALayer for documentation on these common properties.
*/
@@ -558,9 +562,13 @@ 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
* (table or collection view, for example), the container view must be notified to relayout.
* For ASTableView and ASCollectionView, an empty batch editing transaction must be triggered to animate to new row / item sizes.
* 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;
@@ -585,7 +593,6 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
@property (atomic, assign) CGFloat contentsScale; // default=1.0f. See @contentsScaleForDisplay for more info
@property (atomic, assign) CATransform3D transform; // default=CATransform3DIdentity
@property (atomic, assign) CATransform3D subnodeTransform; // default=CATransform3DIdentity
@property (atomic, copy, nullable) NSString *name; // default=nil. Use this to tag your layers in the server-recurse-description / pca or for your own purposes
/**
* @abstract The node view's background color.
@@ -649,6 +656,7 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
* @param node The node to be added.
*/
- (void)addSubnode:(ASDisplayNode *)node;
- (NSString *)name;
@end
/** CALayer(AsyncDisplayKit) defines convenience method for adding sub-ASDisplayNode to a CALayer. */
@@ -659,6 +667,7 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
* @param node The node to be added.
*/
- (void)addSubnode:(ASDisplayNode *)node;
- (NSString *)name;
@end

View File

@@ -19,6 +19,7 @@
#import "_ASDisplayView.h"
#import "_ASScopeTimer.h"
#import "ASDisplayNodeExtras.h"
#import "ASEqualityHelpers.h"
#import "ASInternalHelpers.h"
#import "ASLayout.h"
@@ -47,6 +48,7 @@
// these dynamic properties all defined in ASLayoutOptionsPrivate.m
@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition, layoutOptions;
@synthesize name = _name;
@synthesize preferredFrameSize = _preferredFrameSize;
@synthesize isFinalLayoutable = _isFinalLayoutable;
@@ -458,9 +460,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
_layer = _view.layer;
}
_layer.asyncdisplaykit_node = self;
#if DEBUG
_layer.name = self.description;
#endif
self.asyncLayer.asyncDelegate = self;
{
@@ -526,6 +526,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return (_view != nil || (_flags.layerBacked && _layer != nil));
}
- (NSString *)name
{
ASDN::MutexLocker l(_propertyLock);
return _name;
}
- (void)setName:(NSString *)name
{
ASDN::MutexLocker l(_propertyLock);
if (!ASObjectIsEqual(_name, name)) {
_name = [name copy];
}
}
- (BOOL)isSynchronous
{
return _flags.synchronous;
@@ -2137,6 +2151,18 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
return subtree;
}
#pragma mark - ASLayoutableAsciiArtProtocol
- (NSString *)asciiArtString
{
return [ASLayoutSpec asciiArtStringForChildren:@[] parentName:[self asciiArtName]];
}
- (NSString *)asciiArtName
{
return NSStringFromClass([self class]);
}
@end
// We use associated objects as a last resort if our view is not a _ASDisplayView ie it doesn't have the _node ivar to write to
@@ -2175,6 +2201,11 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode";
}
}
- (NSString *)name
{
return self.asyncdisplaykit_node.name;
}
@end
@implementation CALayer (AsyncDisplayKit)
@@ -2184,6 +2215,11 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode";
[self addSublayer:node.layer];
}
- (NSString *)name
{
return self.asyncdisplaykit_node.name;
}
@end

View File

@@ -33,6 +33,20 @@ typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) {
* Indicates that the best image identifier changed before a download for a worse identifier began.
*/
ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged,
/**
* Indicates that the Photos framework returned no image and no error.
* This may happen if the image is in iCloud and the user did not specify `allowsNetworkAccess`
* in their image request.
*/
ASMultiplexImageNodeErrorCodePhotosImageManagerFailedWithoutError,
/**
* Indicates that the image node could not retrieve the PHAsset for a given asset identifier.
* This typically means that the user has not given Photos framework permissions yet or the asset
* has been removed from the device.
*/
ASMultiplexImageNodeErrorCodePHAssetIsUnavailable
};

View File

@@ -577,8 +577,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
}
if (imageAsset == nil) {
// Error.
completionBlock(nil, nil);
NSError *error = [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodePHAssetIsUnavailable userInfo:nil];
completionBlock(nil, error);
return;
}
@@ -597,15 +597,25 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
PHImageManager *imageManager = strongSelf.imageManager ?: PHImageManager.defaultManager;
[imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) {
NSError *error = info[PHImageErrorKey];
if (error == nil && image == nil) {
error = [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodePhotosImageManagerFailedWithoutError userInfo:nil];
}
if (NSThread.isMainThread) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
completionBlock(image, info[PHImageErrorKey]);
completionBlock(image, error);
});
} else {
completionBlock(image, info[PHImageErrorKey]);
completionBlock(image, error);
}
}];
}];
if (AS_AT_LEAST_IOS8) {
// If you don't set this, iOS will sometimes infer NSQualityOfServiceUserInteractive and promote the entire queue to that level, damaging system responsiveness
newImageRequestOp.qualityOfService = NSQualityOfServiceUserInitiated;
}
_phImageRequestOperation = newImageRequestOp;
[phImageRequestQueue addOperation:newImageRequestOp];
}
@@ -673,7 +683,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
{
// If we failed to load, we stop the loading process.
// Note that if we bailed before we began downloading because the best identifier changed, we don't bail, but rather just begin loading the best image identifier.
if (error && error.code != ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged)
if (error && !([error.domain isEqual:ASMultiplexImageNodeErrorDomain] && error.code == ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged))
return;
OSSpinLockLock(&_imageIdentifiersLock);

View File

@@ -94,6 +94,14 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)reloadData;
/**
* Reload everything from scratch entirely on the main thread, destroying the working range and all cached nodes.
*
* @warning This method is substantially more expensive than UITableView's version and will block the main thread while
* all the cells load.
*/
- (void)reloadDataImmediately;
/**
* begins a batch of insert, delete reload and move operations. This method must be called from the main thread.
*/
@@ -207,6 +215,18 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)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.
*

View File

@@ -7,9 +7,10 @@
*/
#import "ASTableView.h"
#import "ASTableViewInternal.h"
#import "ASAssert.h"
#import "ASDataController.h"
#import "ASChangeSetDataController.h"
#import "ASCollectionViewLayoutController.h"
#import "ASLayoutController.h"
#import "ASRangeController.h"
@@ -135,6 +136,16 @@ static BOOL _isInterceptedSelector(SEL sel)
[super didTransitionToState:state];
}
- (void)setSelected:(BOOL)selected
{
_node.selected = selected;
}
- (void)setHighlighted:(BOOL)highlighted
{
_node.highlighted = highlighted;
}
@end
@@ -145,7 +156,6 @@ static BOOL _isInterceptedSelector(SEL sel)
_ASTableViewProxy *_proxyDataSource;
_ASTableViewProxy *_proxyDelegate;
ASDataController *_dataController;
ASFlowLayoutController *_layoutController;
ASRangeController *_rangeController;
@@ -159,11 +169,12 @@ static BOOL _isInterceptedSelector(SEL sel)
NSIndexPath *_contentOffsetAdjustmentTopVisibleRow;
CGFloat _contentOffsetAdjustment;
CGFloat _maxWidthForNodesConstrainedSize;
BOOL _ignoreMaxWidthChange;
CGFloat _nodesConstrainedWidth;
BOOL _ignoreNodesConstrainedWidthChange;
}
@property (atomic, assign) BOOL asyncDataSourceLocked;
@property (nonatomic, retain, readwrite) ASDataController *dataController;
@end
@@ -189,24 +200,29 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
}
}
+ (Class)dataControllerClass
{
return [ASChangeSetDataController class];
}
#pragma mark -
#pragma mark Lifecycle
- (void)configureWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
- (void)configureWithDataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetching
{
_layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical];
_rangeController = [[ASRangeController alloc] init];
_rangeController.layoutController = _layoutController;
_rangeController.delegate = self;
_dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled];
_dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:asyncDataFetching];
_dataController.dataSource = self;
_dataController.delegate = _rangeController;
_layoutController.dataSource = _dataController;
_asyncDataFetchingEnabled = asyncDataFetchingEnabled;
_asyncDataFetchingEnabled = asyncDataFetching;
_asyncDataSourceLocked = NO;
_leadingScreensForBatching = 1.0;
@@ -214,10 +230,10 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
_automaticallyAdjustsContentOffset = NO;
_maxWidthForNodesConstrainedSize = self.bounds.size.width;
_nodesConstrainedWidth = self.bounds.size.width;
// If the initial size is 0, expect a size change very soon which is part of the initial configuration
// and should not trigger a relayout.
_ignoreMaxWidthChange = (_maxWidthForNodesConstrainedSize == 0);
_ignoreNodesConstrainedWidthChange = (_nodesConstrainedWidth == 0);
}
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
@@ -226,6 +242,11 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
}
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled
{
return [self initWithFrame:frame style:style dataControllerClass:[self.class dataControllerClass] asyncDataFetching:asyncDataFetchingEnabled];
}
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled
{
if (!(self = [super initWithFrame:frame style:style]))
return nil;
@@ -234,8 +255,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
// https://github.com/facebook/AsyncDisplayKit/issues/385
asyncDataFetchingEnabled = NO;
[self configureWithAsyncDataFetching:asyncDataFetchingEnabled];
[self configureWithDataControllerClass:dataControllerClass asyncDataFetching:asyncDataFetchingEnabled];
return self;
}
@@ -244,7 +265,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
if (!(self = [super initWithCoder:aDecoder]))
return nil;
[self configureWithAsyncDataFetching:NO];
[self configureWithDataControllerClass:[self.class dataControllerClass] asyncDataFetching:NO];
return self;
}
@@ -324,6 +345,13 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
[self reloadDataWithCompletion:nil];
}
- (void)reloadDataImmediately
{
ASDisplayNodeAssertMainThread();
[_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone];
[super reloadData];
}
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
{
[_layoutController setTuningParameters:tuningParameters forRangeType:rangeType];
@@ -386,21 +414,21 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
- (void)layoutSubviews
{
if (_maxWidthForNodesConstrainedSize != self.bounds.size.width) {
_maxWidthForNodesConstrainedSize = self.bounds.size.width;
if (_nodesConstrainedWidth != self.bounds.size.width) {
_nodesConstrainedWidth = self.bounds.size.width;
// First width change occurs during initial configuration. An expensive relayout pass is unnecessary at that time
// and should be avoided, assuming that the initial data loading automatically runs shortly afterward.
if (_ignoreMaxWidthChange) {
_ignoreMaxWidthChange = NO;
if (_ignoreNodesConstrainedWidthChange) {
_ignoreNodesConstrainedWidthChange = NO;
} else {
[self beginUpdates];
[_dataController relayoutAllRows];
[_dataController relayoutAllNodes];
[self endUpdates];
}
}
// To ensure _maxWidthForNodesConstrainedSize is up-to-date for every usage, this call to super must be done last
// To ensure _nodesConstrainedWidth is up-to-date for every usage, this call to super must be done last
[super layoutSubviews];
}
@@ -449,6 +477,14 @@ 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();
@@ -818,8 +854,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{
return ASSizeRangeMake(CGSizeMake(_maxWidthForNodesConstrainedSize, 0),
CGSizeMake(_maxWidthForNodesConstrainedSize, FLT_MAX));
return ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, 0),
CGSizeMake(_nodesConstrainedWidth, FLT_MAX));
}
- (void)dataControllerLockDataSource
@@ -849,7 +885,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
return [_asyncDataSource tableView:self numberOfRowsInSection:section];
}
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController
{
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
return [_asyncDataSource numberOfSectionsInTableView:self];

View File

@@ -0,0 +1,31 @@
//
// ASTableViewInternal.h
// AsyncDisplayKit
//
// Created by Huy Nguyen on 26/10/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import "ASTableView.h"
@class ASDataController;
@interface ASTableView (Internal)
@property (nonatomic, retain, readonly) ASDataController *dataController;
/**
* Initializer.
*
* @param frame A rectangle specifying the initial location and size of the table view in its superview€™s coordinates.
* The frame of the table view changes as table cells are added and deleted.
*
* @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants.
*
* @param dataControllerClass A controller class injected to and used to create a data controller for the table view.
*
* @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op.
*/
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled;
@end

View File

@@ -20,7 +20,7 @@
- (instancetype)initWithNode:(ASDisplayNode *)node
{
if (!(self = [super init])) {
if (!(self = [super initWithNibName:nil bundle:nil])) {
return nil;
}

View File

@@ -0,0 +1,23 @@
//
// ASChangeSetDataController.h
// AsyncDisplayKit
//
// Created by Huy Nguyen on 19/10/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/ASDataController.h>
/**
* @abstract Subclass of ASDataController that simulates ordering of operations in batch updates defined in UITableView and UICollectionView.
*
* @discussion The ordering is achieved by using _ASHierarchyChangeSet to enqueue and sort operations.
* More information about the ordering and the index paths used for operations can be found here:
* https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/ManageInsertDeleteRow/ManageInsertDeleteRow.html#//apple_ref/doc/uid/TP40007451-CH10-SW17
*
* @see ASDataController
* @see _ASHierarchyChangeSet
*/
@interface ASChangeSetDataController : ASDataController
@end

View File

@@ -0,0 +1,179 @@
//
// ASChangeSetDataController.m
// AsyncDisplayKit
//
// Created by Huy Nguyen on 19/10/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import "ASChangeSetDataController.h"
#import "ASInternalHelpers.h"
#import "_ASHierarchyChangeSet.h"
#import "ASAssert.h"
@interface ASChangeSetDataController ()
@property (nonatomic, assign) NSUInteger batchUpdateCounter;
@property (nonatomic, strong) _ASHierarchyChangeSet *changeSet;
@end
@implementation ASChangeSetDataController
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
{
if (!(self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled])) {
return nil;
}
_batchUpdateCounter = 0;
return self;
}
#pragma mark - Batching (External API)
- (void)beginUpdates
{
ASDisplayNodeAssertMainThread();
if (_batchUpdateCounter == 0) {
_changeSet = [_ASHierarchyChangeSet new];
}
_batchUpdateCounter++;
}
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
{
ASDisplayNodeAssertMainThread();
_batchUpdateCounter--;
if (_batchUpdateCounter == 0) {
[_changeSet markCompleted];
[super beginUpdates];
for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) {
[super reloadSections:change.indexSet withAnimationOptions:change.animationOptions];
}
for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) {
[super reloadRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
}
for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) {
[super deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
}
for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) {
[super deleteSections:change.indexSet withAnimationOptions:change.animationOptions];
}
for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) {
[super insertSections:change.indexSet withAnimationOptions:change.animationOptions];
}
for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) {
[super insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
}
[super endUpdatesAnimated:animated completion:completion];
_changeSet = nil;
}
}
- (BOOL)batchUpdating
{
BOOL batchUpdating = (_batchUpdateCounter != 0);
// _changeSet must be available during batch update
ASDisplayNodeAssertTrue(batchUpdating == (_changeSet != nil));
return batchUpdating;
}
#pragma mark - Section Editing (External API)
- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if ([self batchUpdating]) {
[_changeSet insertSections:sections animationOptions:animationOptions];
} else {
[super insertSections:sections withAnimationOptions:animationOptions];
}
}
- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if ([self batchUpdating]) {
[_changeSet deleteSections:sections animationOptions:animationOptions];
} else {
[super deleteSections:sections withAnimationOptions:animationOptions];
}
}
- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if ([self batchUpdating]) {
[_changeSet reloadSections:sections animationOptions:animationOptions];
} else {
[super reloadSections:sections withAnimationOptions:animationOptions];
}
}
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if ([self batchUpdating]) {
[_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions];
[_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions];
} else {
[super moveSection:section toSection:newSection withAnimationOptions:animationOptions];
}
}
#pragma mark - Row Editing (External API)
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if ([self batchUpdating]) {
[_changeSet insertItems:indexPaths animationOptions:animationOptions];
} else {
[super insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
}
}
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if ([self batchUpdating]) {
[_changeSet deleteItems:indexPaths animationOptions:animationOptions];
} else {
[super deleteRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
}
}
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if ([self batchUpdating]) {
[_changeSet reloadItems:indexPaths animationOptions:animationOptions];
} else {
[super reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
}
}
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if ([self batchUpdating]) {
[_changeSet deleteItems:@[indexPath] animationOptions:animationOptions];
[_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions];
} else {
[super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions];
}
}
@end

View File

@@ -0,0 +1,39 @@
/* Copyright (c) 2015-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 <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASChangeSetDataController.h>
#import <AsyncDisplayKit/ASDimension.h>
@class ASDisplayNode;
@class ASCollectionDataController;
@protocol ASDataControllerSource;
@protocol ASCollectionDataControllerSource <ASDataControllerSource>
- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
The constrained size range for layout.
*/
- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController;
- (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind;
- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
@end
@interface ASCollectionDataController : ASChangeSetDataController
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
@end

View File

@@ -0,0 +1,217 @@
/* Copyright (c) 2015-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 "ASCollectionDataController.h"
#import "ASAssert.h"
#import "ASMultidimensionalArrayUtils.h"
#import "ASDisplayNode.h"
#import "ASDisplayNodeInternal.h"
#import "ASDataController+Subclasses.h"
//#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...)
@interface ASCollectionDataController ()
- (id<ASCollectionDataControllerSource>)collectionDataSource;
@end
@implementation ASCollectionDataController {
NSMutableDictionary *_pendingNodes;
NSMutableDictionary *_pendingIndexPaths;
}
- (void)prepareForReloadData
{
_pendingNodes = [NSMutableDictionary dictionary];
_pendingIndexPaths = [NSMutableDictionary dictionary];
[[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) {
LOG(@"Populating elements of kind: %@", kind);
NSMutableArray *indexPaths = [NSMutableArray array];
NSMutableArray *nodes = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths];
}];
}
- (void)willReloadData
{
[_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
// Remove everything that existed before the reload, now that we're ready to insert replacements
NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind];
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
NSArray *editingNodes = [self editingNodesOfKind:kind];
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
[self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil];
// Insert each section
NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind];
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
[sections addObject:[NSMutableArray array]];
}
[self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil];
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
[_pendingNodes removeObjectForKey:kind];
[_pendingIndexPaths removeObjectForKey:kind];
}];
}
- (void)prepareForInsertSections:(NSIndexSet *)sections
{
[[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) {
LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections);
NSMutableArray *nodes = [NSMutableArray array];
NSMutableArray *indexPaths = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths];
}];
}
- (void)willInsertSections:(NSIndexSet *)sections
{
[_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]];
}
[self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil];
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:nil];
_pendingNodes[kind] = nil;
_pendingIndexPaths[kind] = nil;
}];
}
- (void)willDeleteSections:(NSIndexSet *)sections
{
[[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections);
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
[self deleteSectionsOfKind:kind atIndexSet:sections completion:nil];
}];
}
- (void)prepareForReloadSections:(NSIndexSet *)sections
{
[[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) {
NSMutableArray *nodes = [NSMutableArray array];
NSMutableArray *indexPaths = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths];
}];
}
- (void)willReloadSections:(NSIndexSet *)sections
{
[_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections);
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
// reinsert the elements
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:nil];
_pendingNodes[kind] = nil;
_pendingIndexPaths[kind] = nil;
}];
}
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
{
[[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths);
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
// update the section of indexpaths
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection];
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
[updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]];
}];
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
}
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths
{
NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind];
for (NSUInteger i = 0; i < sectionCount; i++) {
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i];
NSUInteger rowCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:i];
for (NSUInteger j = 0; j < rowCount; j++) {
NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j];
[indexPaths addObject:indexPath];
[nodes addObject:[self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]];
}
}
}
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths
{
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSUInteger rowNum = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:idx];
NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx];
for (NSUInteger i = 0; i < rowNum; i++) {
NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i];
[indexPaths addObject:indexPath];
[nodes addObject:[self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]];
}
}];
}
#pragma mark - Sizing query
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
if ([kind isEqualToString:ASDataControllerRowNodeKind]) {
return [super constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
} else {
return [self.collectionDataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath];
}
}
#pragma mark - External supplementary store querying
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
ASDisplayNodeAssertMainThread();
return [self completedNodesOfKind:kind][indexPath.section][indexPath.item];
}
#pragma mark - Private Helpers
- (NSArray *)supplementaryKinds
{
return [self.collectionDataSource supplementaryNodeKindsInDataController:self];
}
- (id<ASCollectionDataControllerSource>)collectionDataSource
{
return (id<ASCollectionDataControllerSource>)self.dataSource;
}
@end

View File

@@ -0,0 +1,48 @@
/* Copyright (c) 2015-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 <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASDimension.h>
@class ASCollectionView;
@protocol ASCollectionViewDelegate;
@protocol ASCollectionViewLayoutInspecting <NSObject>
/**
* Asks the inspector to provide a constrained size range for the given supplementary node.
*/
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
* Asks the inspector for the number of supplementary sections in the collection view for the given kind.
*/
- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind;
/**
* Asks the inspector for the number of supplementary views for the given kind in the specified section.
*/
- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
/**
* Allow the inspector to respond to delegate changes.
*
* @discussion A great time to update perform selector caches!
*/
- (void)didChangeCollectionViewDelegate:(id<ASCollectionViewDelegate>)delegate;
@end
@interface ASCollectionViewFlowLayoutInspector : NSObject <ASCollectionViewLayoutInspecting>
@property (nonatomic, weak) UICollectionViewFlowLayout *layout;
- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout;
@end

View File

@@ -0,0 +1,123 @@
/* Copyright (c) 2015-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 <UIKit/UIKit.h>
#import "ASCollectionViewFlowLayoutInspector.h"
#import "ASCollectionView.h"
#import "ASAssert.h"
#import "ASEqualityHelpers.h"
@implementation ASCollectionViewFlowLayoutInspector {
BOOL _delegateImplementsReferenceSizeForHeader;
BOOL _delegateImplementsReferenceSizeForFooter;
}
#pragma mark - Accessors
- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout;
{
self = [super init];
if (flowLayout == nil) {
ASDisplayNodeAssert(NO, @"Should never create a layout inspector without a layout");
}
if (self != nil) {
[self didChangeCollectionViewDelegate:collectionView.asyncDelegate];
_layout = flowLayout;
}
return self;
}
- (void)didChangeCollectionViewDelegate:(id<ASCollectionViewDelegate>)delegate;
{
if (delegate == nil) {
_delegateImplementsReferenceSizeForHeader = NO;
_delegateImplementsReferenceSizeForFooter = NO;
} else {
_delegateImplementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)];
_delegateImplementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)];
}
}
#pragma mark - ASCollectionViewLayoutInspecting
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
CGSize constrainedSize;
CGSize supplementarySize = [self sizeForSupplementaryViewOfKind:kind inSection:indexPath.section collectionView:collectionView];
if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) {
constrainedSize = CGSizeMake(collectionView.bounds.size.width, supplementarySize.height);
} else {
constrainedSize = CGSizeMake(supplementarySize.height, collectionView.bounds.size.height);
}
return ASSizeRangeMake(CGSizeZero, constrainedSize);
}
- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind
{
if ([collectionView.asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
return [collectionView.asyncDataSource numberOfSectionsInCollectionView:collectionView];
} else {
return 1;
}
}
- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section
{
return [self layoutHasSupplementaryViewOfKind:kind inSection:section collectionView:collectionView] ? 1 : 0;
}
#pragma mark - Private helpers
- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView
{
if (ASObjectIsEqual(kind, UICollectionElementKindSectionHeader)) {
if (_delegateImplementsReferenceSizeForHeader) {
return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:section];
} else {
return [self.layout headerReferenceSize];
}
} else if (ASObjectIsEqual(kind, UICollectionElementKindSectionFooter)) {
if (_delegateImplementsReferenceSizeForFooter) {
return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:section];
} else {
return [self.layout footerReferenceSize];
}
} else {
return CGSizeZero;
}
}
- (BOOL)layoutHasSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView
{
CGSize size = [self sizeForSupplementaryViewOfKind:kind inSection:section collectionView:collectionView];
if ([self usedLayoutValueForSize:size] > 0) {
return YES;
} else {
return NO;
}
}
- (CGFloat)usedLayoutValueForSize:(CGSize)size
{
if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) {
return size.height;
} else {
return size.width;
}
}
- (id<ASCollectionViewDelegateFlowLayout>)delegateForCollectionView:(ASCollectionView *)collectionView
{
return (id<ASCollectionViewDelegateFlowLayout>)collectionView.asyncDelegate;
}
@end

View File

@@ -137,7 +137,9 @@ typedef struct ASRangeGeometry ASRangeGeometry;
NSMutableSet *indexPathSet = [[NSMutableSet alloc] init];
NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds];
for (UICollectionViewLayoutAttributes *la in layoutAttributes) {
[indexPathSet addObject:la.indexPath];
if (la.representedElementCategory == UICollectionElementCategoryCell) {
[indexPathSet addObject:la.indexPath];
}
}
return indexPathSet;
}

View File

@@ -0,0 +1,159 @@
/* Copyright (c) 2015-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 "ASDataController.h"
@interface ASDataController (Subclasses)
#pragma mark - Internal editing & completed store querying
/**
* Provides a collection of index paths for nodes of the given kind that are currently in the editing store
*/
- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind;
/**
* Read-only access to the underlying editing nodes of the given kind
*/
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind;
/**
* Read only access to the underlying completed nodes of the given kind
*/
- (NSMutableArray *)completedNodesOfKind:(NSString *)kind;
#pragma mark - Node sizing
/**
* Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`.
*/
- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock;
/*
* Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes.
*
* @discussion Once nodes have loaded their views, we can't layout in the background so this is a chance
* to do so immediately on the main thread.
*/
- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths;
/**
* Provides the size range for a specific node during the layout process.
*/
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
#pragma mark - Node & Section Insertion/Deletion API
/**
* Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes.
*/
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock;
/**
* Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes.
*/
- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock;
/**
* Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished.
*/
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock;
/**
* Deletes the given sections of the specified kind in the backing store, calling completion on the main thread when finished.
*/
- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock;
#pragma mark - Data Manipulation Hooks
/**
* Notifies the subclass to perform any work needed before the data controller is reloaded entirely
*
* @discussion This method will be performed before the data controller enters its editing queue.
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*/
- (void)prepareForReloadData;
/**
* Notifies the subclass that the data controller is about to reload its data entirely
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform new node creation like supplementary views
* or header/footer nodes.
*/
- (void)willReloadData;
/**
* Notifies the subclass to perform setup before sections are inserted in the data controller
*
* @discussion This method will be performed before the data controller enters its editing queue.
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*
* @param sections Indices of sections to be inserted
*/
- (void)prepareForInsertSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will insert new sections at the given position
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param sections Indices of sections to be inserted
*/
- (void)willInsertSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will delete sections at the given positions
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param sections Indices of sections to be deleted
*/
- (void)willDeleteSections:(NSIndexSet *)sections;
/**
* Notifies the subclass to perform any work needed before the given sections will be reloaded.
*
* @discussion This method will be performed before the data controller enters its editing queue, usually on the main
* thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*
* @param sections Indices of sections to be reloaded
*/
- (void)prepareForReloadSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will reload the sections in the given index set
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param sections Indices of sections to be reloaded
*/
- (void)willReloadSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will move a section to a new position
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param section Index of current section position
* @param newSection Index of new section position
*/
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection;
@end

View File

@@ -16,6 +16,8 @@ NS_ASSUME_NONNULL_BEGIN
@class ASCellNode;
@class ASDataController;
FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
typedef NSUInteger ASDataControllerAnimationOptions;
/**
@@ -42,7 +44,7 @@ typedef NSUInteger ASDataControllerAnimationOptions;
/**
Fetch the number of sections.
*/
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController;
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController;
/**
Lock the data source for data fetching.
@@ -92,13 +94,12 @@ typedef NSUInteger ASDataControllerAnimationOptions;
@end
/**
* Controller to layout data in background, and managed data updating.
*
* All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the data
* will be updated asynchronously. The dataSource must be updated to reflect the changes before these methods has been called.
* For each data updatin, the corresponding methods in delegate will be called.
* For each data updating, the corresponding methods in delegate will be called.
*/
@protocol ASFlowLayoutControllerDataSource;
@interface ASDataController : ASDealloc2MainObject <ASFlowLayoutControllerDataSource>
@@ -158,15 +159,19 @@ typedef NSUInteger ASDataControllerAnimationOptions;
- (void)reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
* Re-measures all loaded nodes. Used to respond to a change in size of the containing view
* Re-measures all loaded nodes in the backing store.
*
* @discussion Used to respond to a change in size of the containing view
* (e.g. ASTableView or ASCollectionView after an orientation change).
*/
- (void)relayoutAllRows;
- (void)relayoutAllNodes;
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^ _Nullable)())completion;
- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/** @name Data Querying */
- (NSUInteger)numberOfSections;
@@ -179,7 +184,10 @@ typedef NSUInteger ASDataControllerAnimationOptions;
- (NSArray<ASCellNode *> *)nodesAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes; // This provides efficient access to the entire _completedNodes multidimensional array.
/**
* Direct access to the nodes that have completed calculation and layout
*/
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes;
@end

View File

@@ -15,18 +15,23 @@
#import "ASDisplayNode.h"
#import "ASMultidimensionalArrayUtils.h"
#import "ASDisplayNodeInternal.h"
#import "ASLayout.h"
//#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...)
const static NSUInteger kASDataControllerSizingCountPerProcessor = 5;
NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
static void *kASSizingQueueContext = &kASSizingQueueContext;
@interface ASDataController () {
NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available.
NSMutableArray *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable.
NSMutableArray *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes.
NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable.
NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes.
NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking.
NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes.
@@ -52,9 +57,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
return nil;
}
_completedNodes = [NSMutableArray array];
_editingNodes = [NSMutableArray array];
_completedNodes = [NSMutableDictionary dictionary];
_editingNodes = [NSMutableDictionary dictionary];
_completedNodes[ASDataControllerRowNodeKind] = [NSMutableArray array];
_editingNodes[ASDataControllerRowNodeKind] = [NSMutableArray array];
_pendingEditCommandBlocks = [NSMutableArray array];
_editingTransactionQueue = [[NSOperationQueue alloc] init];
@@ -96,29 +104,54 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - Cell Layout
/*
* FIXME: Shouldn't this method, as well as `_layoutNodes:atIndexPaths:withAnimationOptions:` use the word "measure" instead?
*
* Once nodes have loaded their views, we can't layout in the background so this is a chance
* to do so immediately on the main thread.
*/
- (void)_layoutNodesWithMainThreadAffinity:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths {
- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{
NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
// Processing in batches
for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) {
NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize));
NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange];
NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange];
[self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock];
}
}
- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths {
NSAssert(NSThread.isMainThread, @"Main thread layout must be on the main thread.");
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, __unused BOOL * stop) {
ASCellNode *node = nodes[idx];
if (node.isNodeLoaded) {
ASSizeRange constrainedSize = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath];
[node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
[self _layoutNode:node withConstrainedSize:constrainedSize];
}
}];
}
- (void)_layoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
/**
* Measure and layout the given node with the constrained size range.
*/
- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize
{
[node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
}
/**
* Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store.
*/
- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
// Insert finished nodes into data storage
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}
- (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"Cell node layout must be initiated from edit transaction queue");
if (!nodes.count) {
return;
}
@@ -131,7 +164,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
for (NSUInteger k = j; k < j + batchCount; k++) {
ASCellNode *node = nodes[k];
if (!node.isNodeLoaded) {
nodeBoundSizes[k] = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPaths[k]];
nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]];
}
}
@@ -139,11 +172,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
for (NSUInteger k = j; k < j + batchCount; k++) {
ASCellNode *node = nodes[k];
// Only measure nodes whose views aren't loaded, since we're in the background.
// We should already have measured loaded nodes before we left the main thread, using _layoutNodesWithMainThreadAffinity:
// We should already have measured loaded nodes before we left the main thread, using layoutLoadedNodes:ofKind:atIndexPaths:
if (!node.isNodeLoaded) {
ASSizeRange constrainedSize = nodeBoundSizes[k];
[node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
[self _layoutNode:node withConstrainedSize:nodeBoundSizes[k]];
}
}
});
@@ -152,83 +183,150 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated.
dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER);
free(nodeBoundSizes);
// Insert finished nodes into data storage
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}
- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
// Processing in batches
for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) {
NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize));
NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange];
NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange];
[self _layoutNodes:batchedNodes atIndexPaths:batchedIndexPaths withAnimationOptions:animationOptions];
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
}
#pragma mark - Internal Data Querying + Editing
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
return [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath];
}
- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
#pragma mark - External Data Querying + Editing
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{
if (indexPaths.count == 0)
return;
ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(_editingNodes, indexPaths, nodes);
NSMutableArray *editingNodes = _editingNodes[kind];
ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes);
_editingNodes[kind] = editingNodes;
// 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);
NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes);
ASDisplayNodePerformBlockOnMainThread(^{
_completedNodes = completedNodes;
if (_delegateDidInsertNodes)
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
_completedNodes[kind] = completedNodes;
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
});
}
- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{
if (indexPaths.count == 0)
if (indexPaths.count == 0) {
return;
LOG(@"_deleteNodesAtIndexPaths:%@, full index paths in _editingNodes = %@", indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes));
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, indexPaths);
}
LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind]));
NSMutableArray *editingNodes = _editingNodes[kind];
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths);
_editingNodes[kind] = editingNodes;
ASDisplayNodePerformBlockOnMainThread(^{
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, indexPaths);
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, indexPaths);
if (_delegateDidDeleteNodes)
[_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths);
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths);
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
});
}
- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock
{
if (indexSet.count == 0)
return;
[_editingNodes insertObjects:sections atIndexes:indexSet];
if (_editingNodes[kind] == nil) {
_editingNodes[kind] = [NSMutableArray array];
}
[_editingNodes[kind] insertObjects:sections atIndexes:indexSet];
// 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(^{
[_completedNodes insertObjects:sectionsForCompleted atIndexes:indexSet];
if (_delegateDidInsertSections)
[_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions];
[_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet];
if (completionBlock) {
completionBlock(sections, indexSet);
}
});
}
- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock
{
if (indexSet.count == 0)
return;
[_editingNodes removeObjectsAtIndexes:indexSet];
[_editingNodes[kind] removeObjectsAtIndexes:indexSet];
ASDisplayNodePerformBlockOnMainThread(^{
[_completedNodes removeObjectsAtIndexes:indexSet];
[_completedNodes[kind] removeObjectsAtIndexes:indexSet];
if (completionBlock) {
completionBlock(indexSet);
}
});
}
#pragma mark - Internal Data Querying + Editing
/**
* Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes.
*
* @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy
* of the editing nodes. The delegate is invoked on the main thread.
*/
- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
if (_delegateDidInsertNodes)
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}
/**
* Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed.
*
* @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread.
* Once the backing stores are consistent, the delegate is invoked on the main thread.
*/
- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
if (_delegateDidDeleteNodes)
[_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}
/**
* Inserts sections, represented as arrays, into the backing store at the given indicies and notifies the delegate.
*
* @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted
* in the completed store on the main thread. The delegate is invoked on the main thread.
*/
- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) {
if (_delegateDidInsertSections)
[_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions];
}];
}
/**
* Removes sections at the given indicies from the backing store and notifies the delegate.
*
* @discussion Section array are first removed from the editing store, then the associated section in the completed
* store is removed on the main thread. The delegate is invoked on the main thread.
*/
- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) {
if (_delegateDidDeleteSections)
[_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
});
}];
}
#pragma mark - Initial Load & Full Reload (External API)
@@ -239,7 +337,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
ASDisplayNodeAssertMainThread();
[self accessDataSourceWithBlock:^{
NSMutableArray *indexPaths = [NSMutableArray array];
NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self];
NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self];
// insert sections
[self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0];
@@ -260,30 +358,46 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}
- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion
{
[self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion];
}
- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil];
}
- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{
NSUInteger sectionCount = [_dataSource dataControllerNumberOfSections:self];
[self accessDataSourceSynchronously:synchronously withBlock:^{
NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self];
NSMutableArray *updatedNodes = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
// Measure nodes whose views are loaded before we leave the main thread
[self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths];
[self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths];
// Allow subclasses to perform setup before going into the edit transaction
[self prepareForReloadData];
[_editingTransactionQueue addOperationWithBlock:^{
void (^transactionBlock)() = ^{
LOG(@"Edit Transaction - reloadData");
// Remove everything that existed before the reload, now that we're ready to insert replacements
NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _editingNodes.count)];
NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
[self willReloadData];
// Insert each section
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
@@ -297,16 +411,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
if (completion) {
dispatch_async(dispatch_get_main_queue(), completion);
}
}];
};
if (synchronously) {
transactionBlock();
} else {
[_editingTransactionQueue addOperationWithBlock:transactionBlock];
}
}];
}];
}
#pragma mark - Data Source Access (Calling _dataSource)
/**
* Safely locks access to the data source and executes the given block, unlocking once complete.
*
* @discussion When `asyncDataFetching` is enabled, the block is executed on a background thread.
*/
- (void)accessDataSourceWithBlock:(dispatch_block_t)block
{
if (_asyncDataFetchingEnabled) {
[self accessDataSourceSynchronously:NO withBlock:block];
}
- (void)accessDataSourceSynchronously:(BOOL)synchronously withBlock:(dispatch_block_t)block
{
if (!synchronously && _asyncDataFetchingEnabled) {
[_dataSource dataControllerLockDataSource];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
block();
@@ -319,6 +449,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}
}
/**
* Fetches row nodes and their specified index paths for the provided sections from the data source.
*
* @discussion Results are stored in the passed mutable arrays.
*/
- (void)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet mutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths
{
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
@@ -333,9 +468,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}];
}
/**
* Fetches row nodes and their specified index paths for all sections from the data source.
*
* @discussion Results are stored in the passed mutable arrays.
*/
- (void)_populateFromEntireDataSourceWithMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths
{
NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self];
NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self];
for (NSUInteger i = 0; i < sectionNum; i++) {
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i];
@@ -375,7 +515,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
ASDisplayNodePerformBlockOnMainThread(^{
// 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);
_externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]);
LOG(@"endUpdatesWithCompletion - begin updates call to delegate");
[_delegate dataControllerBeginUpdates:self];
@@ -403,6 +543,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}
}
/**
* Queues the given operation until an `endUpdates` synchronize update is completed.
*
* If this method is called outside of a begin/endUpdates batch update, the block is
* executed immediately.
*/
- (void)performEditCommandWithBlock:(void (^)(void))block
{
// This method needs to block the thread and synchronously perform the operation if we are not
@@ -416,49 +562,55 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - Section Editing (External API)
- (void)insertSections:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - insertSections: %@", indexSet);
LOG(@"Edit Command - insertSections: %@", sections);
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{
NSMutableArray *updatedNodes = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromDataSourceWithSectionIndexSet:indexSet mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
[self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
// Measure nodes whose views are loaded before we leave the main thread
[self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths];
[self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths];
[self prepareForInsertSections:sections];
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - insertSections: %@", indexSet);
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:indexSet.count];
for (NSUInteger i = 0; i < indexSet.count; i++) {
[self willInsertSections:sections];
LOG(@"Edit Transaction - insertSections: %@", sections);
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]];
}
[self _insertSections:sectionArray atIndexSet:indexSet withAnimationOptions:animationOptions];
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
[self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
}];
}];
}];
}
- (void)deleteSections:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - deleteSections: %@", indexSet);
LOG(@"Edit Command - deleteSections: %@", sections);
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
[_editingTransactionQueue addOperationWithBlock:^{
[self willDeleteSections:sections];
// remove elements
LOG(@"Edit Transaction - deleteSections: %@", indexSet);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, indexSet);
LOG(@"Edit Transaction - deleteSections: %@", sections);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
}];
}];
}
@@ -481,12 +633,16 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// at this time. Thus _editingNodes could be empty and crash in ASIndexPathsForMultidimensional[...]
// Measure nodes whose views are loaded before we leave the main thread
[self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths];
[self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths];
[self prepareForReloadSections:sections];
[_editingTransactionQueue addOperationWithBlock:^{
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, sections);
[self willReloadSections:sections];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes));
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]));
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
@@ -506,12 +662,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
[_editingTransactionQueue addOperationWithBlock:^{
[self willMoveSection:section toSection:newSection];
// remove elements
LOG(@"Edit Transaction - moveSection");
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, indexPaths);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// update the section of indexpaths
@@ -527,6 +685,49 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}];
}
#pragma mark - Backing store manipulation optional hooks (Subclass API)
- (void)prepareForReloadData
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willReloadData
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)prepareForInsertSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willInsertSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willDeleteSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)prepareForReloadSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willReloadSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
#pragma mark - Row Editing (External API)
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
@@ -546,7 +747,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}
// Measure nodes whose views are loaded before we leave the main thread
[self _layoutNodesWithMainThreadAffinity:nodes atIndexPaths:indexPaths];
[self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths];
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - insertRows: %@", indexPaths);
@@ -596,7 +797,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}
// Measure nodes whose views are loaded before we leave the main thread
[self _layoutNodesWithMainThreadAffinity:nodes atIndexPaths:indexPaths];
[self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths];
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
@@ -607,41 +808,46 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}];
}
- (void)relayoutAllRows
- (void)relayoutAllNodes
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - relayoutRows");
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
void (^relayoutNodesBlock)(NSMutableArray *) = ^void(NSMutableArray *nodes) {
if (!nodes.count) {
return;
}
[self accessDataSourceWithBlock:^{
[nodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) {
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
ASSizeRange constrainedSize = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath];
[node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
}];
}];
}];
};
// Can't relayout right away because _completedNodes may not be up-to-date,
// 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(^{
relayoutNodesBlock(_completedNodes);
for (NSString *kind in [_completedNodes keyEnumerator]) {
[self _relayoutNodesOfKind:kind];
}
});
}];
}];
}
- (void)_relayoutNodesOfKind:(NSString *)kind
{
ASDisplayNodeAssertMainThread();
NSArray *nodes = [self completedNodesOfKind:kind];
if (!nodes.count) {
return;
}
[self accessDataSourceWithBlock:^{
[nodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) {
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
ASLayout *layout = [node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0.0f, 0.0f, layout.size.width, layout.size.height);
}];
}];
}];
}
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
@@ -651,7 +857,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, [NSArray arrayWithObject:indexPath]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]);
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
@@ -662,6 +868,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}];
}
#pragma mark - Data Querying (Subclass API)
- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind
{
return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil;
}
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind
{
return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array];
}
- (NSMutableArray *)completedNodesOfKind:(NSString *)kind
{
return _completedNodes[kind];
}
#pragma mark - Data Querying (External API)
- (NSUInteger)numberOfSections
@@ -711,7 +934,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (NSArray *)completedNodes
{
ASDisplayNodeAssertMainThread();
return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes;
return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind];
}
#pragma mark - Dealloc
@@ -719,15 +942,17 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)dealloc
{
ASDisplayNodeAssertMainThread();
[_completedNodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) {
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) {
if (node.isNodeLoaded) {
if (node.layerBacked) {
[node.layer removeFromSuperlayer];
} else {
[node.view removeFromSuperview];
[_completedNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
[nodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) {
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) {
if (node.isNodeLoaded) {
if (node.layerBacked) {
[node.layer removeFromSuperlayer];
} else {
[node.view removeFromSuperview];
}
}
}
}];
}];
}];
}

View File

@@ -42,7 +42,7 @@ NS_ASSUME_NONNULL_BEGIN
*
* @param contentView UIView to add a (sized) node's view to.
*
* @param node The ASCellNode to be added.
* @param cellNode The cell node to be added.
*/
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node;

View File

@@ -48,7 +48,7 @@
#pragma mark - View manipulation
- (void)moveNode:(ASCellNode *)node toView:(UIView *)view
- (void)moveCellNode:(ASCellNode *)node toView:(UIView *)view
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(node, @"Cannot move a nil node to a view");
@@ -75,7 +75,7 @@
// coalesce these events -- handling them multiple times per runloop is noisy and expensive
_queuedRangeUpdate = YES;
[self performSelector:@selector(updateVisibleNodeIndexPaths)
withObject:nil
afterDelay:0
@@ -158,9 +158,9 @@
return rangeType == ASLayoutRangeTypeRender;
}
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)cellNode
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node
{
if (cellNode.view.superview == contentView) {
if (node.view.superview == contentView) {
// this content view is already correctly configured
return;
}
@@ -170,7 +170,7 @@
[view removeFromSuperview];
}
[self moveNode:cellNode toView:contentView];
[self moveCellNode:node toView:contentView];
}

View File

@@ -32,9 +32,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign) CGFloat shadowRadius;
@property (nonatomic, assign) CGFloat borderWidth;
@property (nonatomic, assign, getter = isOpaque) BOOL opaque;
@property (nullable, nonatomic, retain) __attribute__((NSObject)) CGColorRef borderColor;
@property (nullable, nonatomic, copy) NSString *asyncdisplaykit_name;
@property (nullable, nonatomic, retain) __attribute__((NSObject)) CGColorRef backgroundColor;
@property (nonatomic, retain) __attribute__((NSObject)) CGColorRef borderColor;
@property (nonatomic, retain) __attribute__((NSObject)) CGColorRef backgroundColor;
@property (nonatomic, assign) BOOL allowsEdgeAntialiasing;
@property (nonatomic, assign) unsigned int edgeAntialiasingMask;
@@ -81,9 +80,3 @@ NS_ASSUME_NONNULL_BEGIN
@property (nullable, nonatomic, copy) NSString *accessibilityIdentifier;
@end
@interface CALayer (ASDisplayNodeLayer)
@property (nullable, atomic, copy) NSString *asyncdisplaykit_name;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,13 +0,0 @@
/* 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 "UIView+ASConvenience.h"
@implementation CALayer (ASDisplayNodeLayer)
@dynamic asyncdisplaykit_name;
@end

View File

@@ -36,11 +36,6 @@
_displaySentinel = [[ASSentinel alloc] init];
self.opaque = YES;
#if DEBUG
// This is too expensive to do in production on all layers.
self.name = [NSString stringWithFormat:@"%@ (%p)", NSStringFromClass([self class]), self];
#endif
}
return self;
}

View File

@@ -0,0 +1,59 @@
/*
* 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 <Foundation/Foundation.h>
@protocol ASLayoutableAsciiArtProtocol <NSObject>
/**
* Returns an ascii-art representation of this object and its children.
* For example, an ASInsetSpec may return something like this:
*
* --ASInsetLayoutSpec--
* | ASTextNode |
* ---------------------
*/
- (NSString *)asciiArtString;
/**
* returns the name of this object that will display in the ascii art. Usually this can
* simply be NSStringFromClass([self class]).
*/
- (NSString *)asciiArtName;
@end
/**
* A that takes a parent and its children and renders as ascii art box.
*/
@interface ASAsciiArtBoxCreator : NSObject
/**
* Renders an ascii art box with the children aligned horizontally
* Example:
* ------------ASStackLayoutSpec-----------
* | ASTextNode ASTextNode ASTextNode |
* ----------------------------------------
*/
+ (NSString *)horizontalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent;
/**
* Renders an ascii art box with the children aligned vertically.
* Example:
* --ASStackLayoutSpec--
* | ASTextNode |
* | ASTextNode |
* | ASTextNode |
* ---------------------
*/
+ (NSString *)verticalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent;
@end

View File

@@ -0,0 +1,185 @@
/*
* 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 UIKit;
#import "ASAsciiArtBoxCreator.h"
static const NSUInteger kDebugBoxPadding = 2;
typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation)
{
PIDebugBoxPaddingLocationFront,
PIDebugBoxPaddingLocationEnd,
PIDebugBoxPaddingLocationBoth
};
@interface NSString(PIDebugBox)
@end
@implementation NSString(PIDebugBox)
+ (instancetype)debugbox_stringWithString:(NSString *)stringToRepeat repeatedCount:(NSUInteger)repeatCount
{
NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[stringToRepeat length] * repeatCount];
for (NSUInteger index = 0; index < repeatCount; index++) {
[string appendString:stringToRepeat];
}
return [string copy];
}
- (NSString *)debugbox_stringByAddingPadding:(NSString *)padding count:(NSUInteger)count location:(PIDebugBoxPaddingLocation)location
{
NSString *paddingString = [NSString debugbox_stringWithString:padding repeatedCount:count];
switch (location) {
case PIDebugBoxPaddingLocationFront:
return [NSString stringWithFormat:@"%@%@", paddingString, self];
case PIDebugBoxPaddingLocationEnd:
return [NSString stringWithFormat:@"%@%@", self, paddingString];
case PIDebugBoxPaddingLocationBoth:
return [NSString stringWithFormat:@"%@%@%@", paddingString, self, paddingString];
}
return [self copy];
}
@end
@implementation ASAsciiArtBoxCreator
+ (NSString *)horizontalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent
{
if ([children count] == 0) {
return parent;
}
NSMutableArray *childrenLines = [NSMutableArray array];
// split the children into lines
NSUInteger lineCountPerChild = 0;
for (NSString *child in children) {
NSArray *lines = [child componentsSeparatedByString:@"\n"];
lineCountPerChild = MAX(lineCountPerChild, [lines count]);
}
for (NSString *child in children) {
NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy];
NSUInteger topPadding = ceilf((CGFloat)(lineCountPerChild - [lines count])/2.0);
NSUInteger bottomPadding = (lineCountPerChild - [lines count])/2.0;
NSUInteger lineLength = [lines[0] length];
for (NSUInteger index = 0; index < topPadding; index++) {
[lines insertObject:[NSString debugbox_stringWithString:@" " repeatedCount:lineLength] atIndex:0];
}
for (NSUInteger index = 0; index < bottomPadding; index++) {
[lines addObject:[NSString debugbox_stringWithString:@" " repeatedCount:lineLength]];
}
[childrenLines addObject:lines];
}
NSMutableArray *concatenatedLines = [NSMutableArray array];
NSString *padding = [NSString debugbox_stringWithString:@" " repeatedCount:kDebugBoxPadding];
for (NSUInteger index = 0; index < lineCountPerChild; index++) {
NSMutableString *line = [[NSMutableString alloc] init];
[line appendFormat:@"|%@",padding];
for (NSArray *childLines in childrenLines) {
[line appendFormat:@"%@%@", childLines[index], padding];
}
[line appendString:@"|"];
[concatenatedLines addObject:line];
}
// surround the lines in a box
NSUInteger totalLineLength = [concatenatedLines[0] length];
if (totalLineLength < [parent length]) {
NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - totalLineLength;
NSUInteger leftPadding = ceilf((CGFloat)difference/2.0);
NSUInteger rightPadding = difference/2;
NSString *leftString = [@"|" debugbox_stringByAddingPadding:@" " count:leftPadding location:PIDebugBoxPaddingLocationEnd];
NSString *rightString = [@"|" debugbox_stringByAddingPadding:@" " count:rightPadding location:PIDebugBoxPaddingLocationFront];
NSMutableArray *paddedLines = [NSMutableArray array];
for (NSString *line in concatenatedLines) {
NSString *paddedLine = [line stringByReplacingOccurrencesOfString:@"|" withString:leftString options:NSCaseInsensitiveSearch range:NSMakeRange(0, 1)];
paddedLine = [paddedLine stringByReplacingOccurrencesOfString:@"|" withString:rightString options:NSCaseInsensitiveSearch range:NSMakeRange([paddedLine length] - 1, 1)];
[paddedLines addObject:paddedLine];
}
concatenatedLines = paddedLines;
totalLineLength += difference;
}
concatenatedLines = [self appendTopAndBottomToBoxString:concatenatedLines parent:parent];
return [concatenatedLines componentsJoinedByString:@"\n"];
}
+ (NSString *)verticalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent
{
if ([children count] == 0) {
return parent;
}
NSMutableArray *childrenLines = [NSMutableArray array];
NSUInteger maxChildLength = 0;
for (NSString *child in children) {
NSArray *lines = [child componentsSeparatedByString:@"\n"];
maxChildLength = MAX(maxChildLength, [lines[0] length]);
}
NSUInteger rightPadding = 0;
NSUInteger leftPadding = 0;
if (maxChildLength < [parent length]) {
NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - maxChildLength;
leftPadding = ceilf((CGFloat)difference/2.0);
rightPadding = difference/2;
}
NSString *rightPaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:rightPadding + kDebugBoxPadding];
NSString *leftPaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:leftPadding + kDebugBoxPadding];
for (NSString *child in children) {
NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy];
NSUInteger leftLinePadding = ceilf((CGFloat)(maxChildLength - [lines[0] length])/2.0);
NSUInteger rightLinePadding = (maxChildLength - [lines[0] length])/2.0;
for (NSString *line in lines) {
NSString *rightLinePaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:rightLinePadding];
rightLinePaddingString = [NSString stringWithFormat:@"%@%@|", rightLinePaddingString, rightPaddingString];
NSString *leftLinePaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:leftLinePadding];
leftLinePaddingString = [NSString stringWithFormat:@"|%@%@", leftLinePaddingString, leftPaddingString];
NSString *paddingLine = [NSString stringWithFormat:@"%@%@%@", leftLinePaddingString, line, rightLinePaddingString];
[childrenLines addObject:paddingLine];
}
}
childrenLines = [self appendTopAndBottomToBoxString:childrenLines parent:parent];
return [childrenLines componentsJoinedByString:@"\n"];
}
+ (NSMutableArray *)appendTopAndBottomToBoxString:(NSMutableArray *)boxStrings parent:(NSString *)parent
{
NSUInteger totalLineLength = [boxStrings[0] length];
[boxStrings addObject:[NSString debugbox_stringWithString:@"-" repeatedCount:totalLineLength]];
NSUInteger leftPadding = ceilf(((CGFloat)(totalLineLength - [parent length]))/2.0);
NSUInteger rightPadding = (totalLineLength - [parent length])/2;
NSString *topLine = [parent debugbox_stringByAddingPadding:@"-" count:leftPadding location:PIDebugBoxPaddingLocationFront];
topLine = [topLine debugbox_stringByAddingPadding:@"-" count:rightPadding location:PIDebugBoxPaddingLocationEnd];
[boxStrings insertObject:topLine atIndex:0];
return boxStrings;
}
@end

View File

@@ -9,6 +9,7 @@
*/
#import <AsyncDisplayKit/ASLayoutable.h>
#import <AsyncDisplayKit/ASAsciiArtBoxCreator.h>
NS_ASSUME_NONNULL_BEGIN
@@ -99,5 +100,14 @@ NS_ASSUME_NONNULL_BEGIN
@end
@interface ASLayoutSpec (Debugging) <ASLayoutableAsciiArtProtocol>
/**
* Used by other layout specs to create ascii art debug strings
*/
+ (NSString *)asciiArtStringForChildren:(NSArray *)children parentName:(NSString *)parentName direction:(ASStackLayoutDirection)direction;
+ (NSString *)asciiArtStringForChildren:(NSArray *)children parentName:(NSString *)parentName;
@end
NS_ASSUME_NONNULL_END

View File

@@ -122,5 +122,40 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey";
return self.layoutChildren[kDefaultChildrenKey];
}
@end
@implementation ASLayoutSpec (Debugging)
#pragma mark - ASLayoutableAsciiArtProtocol
+ (NSString *)asciiArtStringForChildren:(NSArray *)children parentName:(NSString *)parentName direction:(ASStackLayoutDirection)direction
{
NSMutableArray *childStrings = [NSMutableArray array];
for (id<ASLayoutableAsciiArtProtocol> layoutChild in children) {
NSString *childString = [layoutChild asciiArtString];
if (childString) {
[childStrings addObject:childString];
}
}
if (direction == ASStackLayoutDirectionHorizontal) {
return [ASAsciiArtBoxCreator horizontalBoxStringForChildren:childStrings parent:parentName];
}
return [ASAsciiArtBoxCreator verticalBoxStringForChildren:childStrings parent:parentName];
}
+ (NSString *)asciiArtStringForChildren:(NSArray *)children parentName:(NSString *)parentName
{
return [self asciiArtStringForChildren:children parentName:parentName direction:ASStackLayoutDirectionHorizontal];
}
- (NSString *)asciiArtString
{
return [ASLayoutSpec asciiArtStringForChildren:@[self.child] parentName:[self asciiArtName]];
}
- (NSString *)asciiArtName
{
return NSStringFromClass([self class]);
}
@end

View File

@@ -73,3 +73,14 @@ static NSString * const kOverlayChildKey = @"kOverlayChildKey";
}
@end
@implementation ASOverlayLayoutSpec (Debugging)
#pragma mark - ASLayoutableAsciiArtProtocol
- (NSString *)debugBoxString
{
return [ASLayoutSpec asciiArtStringForChildren:@[self.overlay, self.child] parentName:[self asciiArtName]];
}
@end

View File

@@ -87,3 +87,14 @@
}
@end
@implementation ASRatioLayoutSpec (Debugging)
#pragma mark - ASLayoutableAsciiArtProtocol
- (NSString *)asciiArtName
{
return [NSString stringWithFormat:@"%@ (%.1f)", NSStringFromClass([self class]), self.ratio];
}
@end

View File

@@ -136,3 +136,14 @@
}
@end
@implementation ASStackLayoutSpec (Debugging)
#pragma mark - ASLayoutableAsciiArtProtocol
- (NSString *)asciiArtString
{
return [ASLayoutSpec asciiArtStringForChildren:self.children parentName:[self asciiArtName] direction:self.direction];
}
@end

View File

@@ -85,3 +85,14 @@
}
@end
@implementation ASStaticLayoutSpec (Debugging)
#pragma mark - ASLayoutableAsciiArtProtocol
- (NSString *)debugBoxString
{
return [ASLayoutSpec asciiArtStringForChildren:self.children parentName:[self asciiArtName]];
}
@end

View File

@@ -496,18 +496,6 @@
_setToLayer(edgeAntialiasingMask, edgeAntialiasingMask);
}
- (NSString *)name
{
_bridge_prologue;
return _getFromLayer(asyncdisplaykit_name);
}
- (void)setName:(NSString *)name
{
_bridge_prologue;
_setToLayer(asyncdisplaykit_name, name);
}
- (BOOL)isAccessibilityElement
{
_bridge_prologue;

View File

@@ -26,3 +26,7 @@ CGFloat ASCeilPixelValue(CGFloat f);
CGFloat ASRoundPixelValue(CGFloat f);
ASDISPLAYNODE_EXTERN_C_END
@interface NSIndexPath (ASInverseComparison)
- (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath;
@end

View File

@@ -70,3 +70,12 @@ CGFloat ASRoundPixelValue(CGFloat f)
{
return roundf(f * ASScreenScale()) / ASScreenScale();
}
@implementation NSIndexPath (ASInverseComparison)
- (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath
{
return [otherIndexPath compare:self];
}
@end

View File

@@ -0,0 +1,72 @@
//
// _ASHierarchyChangeSet.h
// AsyncDisplayKit
//
// Created by Adlai Holler on 9/29/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ASDataController.h"
typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) {
_ASHierarchyChangeTypeReload,
_ASHierarchyChangeTypeDelete,
_ASHierarchyChangeTypeInsert
};
@interface _ASHierarchySectionChange : NSObject
// FIXME: Generalize this to `changeMetadata` dict?
@property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions;
@property (nonatomic, strong, readonly) NSIndexSet *indexSet;
@property (nonatomic, readonly) _ASHierarchyChangeType changeType;
@end
@interface _ASHierarchyItemChange : NSObject
@property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions;
/// Index paths are sorted descending for changeType .Delete, ascending otherwise
@property (nonatomic, strong, readonly) NSArray *indexPaths;
@property (nonatomic, readonly) _ASHierarchyChangeType changeType;
@end
@interface _ASHierarchyChangeSet : NSObject
@property (nonatomic, strong, readonly) NSIndexSet *deletedSections;
@property (nonatomic, strong, readonly) NSIndexSet *insertedSections;
@property (nonatomic, strong, readonly) NSIndexSet *reloadedSections;
@property (nonatomic, strong, readonly) NSArray *insertedItems;
@property (nonatomic, strong, readonly) NSArray *deletedItems;
@property (nonatomic, strong, readonly) NSArray *reloadedItems;
@property (nonatomic, readonly) BOOL completed;
/// Call this once the change set has been constructed to prevent future modifications to the changeset. Calling this more than once is a programmer error.
- (void)markCompleted;
/**
@abstract Return sorted changes of the given type, grouped by animation options.
Items deleted from deleted sections are not reported.
Items inserted into inserted sections are not reported.
Items reloaded in reloaded sections are not reported.
The safe order for processing change groups is:
- Reloaded sections & reloaded items
- Deleted items, descending order
- Deleted sections, descending order
- Inserted sections, ascending order
- Inserted items, ascending order
*/
- (NSArray /*<_ASHierarchySectionChange *>*/ *)sectionChangesOfType:(_ASHierarchyChangeType)changeType;
- (NSArray /*<_ASHierarchyItemChange *>*/ *)itemChangesOfType:(_ASHierarchyChangeType)changeType;
- (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options;
- (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options;
- (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options;
- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
@end

View File

@@ -0,0 +1,337 @@
//
// _ASHierarchyChangeSet.m
// AsyncDisplayKit
//
// Created by Adlai Holler on 9/29/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import "_ASHierarchyChangeSet.h"
#import "ASInternalHelpers.h"
@interface _ASHierarchySectionChange ()
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
On return `changes` is sorted according to the change type with changes coalesced by animationOptions
Assumes: `changes` is [_ASHierarchySectionChange] all with the same changeType
*/
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes;
@end
@interface _ASHierarchyItemChange ()
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted;
/**
On return `changes` is sorted according to the change type with changes coalesced by animationOptions
Assumes: `changes` is [_ASHierarchyItemChange] all with the same changeType
*/
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSections:(NSIndexSet *)sections;
@end
@interface _ASHierarchyChangeSet ()
@property (nonatomic, strong, readonly) NSMutableArray *insertItemChanges;
@property (nonatomic, strong, readonly) NSMutableArray *deleteItemChanges;
@property (nonatomic, strong, readonly) NSMutableArray *reloadItemChanges;
@property (nonatomic, strong, readonly) NSMutableArray *insertSectionChanges;
@property (nonatomic, strong, readonly) NSMutableArray *deleteSectionChanges;
@property (nonatomic, strong, readonly) NSMutableArray *reloadSectionChanges;
@end
@implementation _ASHierarchyChangeSet {
NSMutableIndexSet *_deletedSections;
NSMutableIndexSet *_insertedSections;
NSMutableIndexSet *_reloadedSections;
NSMutableArray *_insertedItems;
NSMutableArray *_deletedItems;
NSMutableArray *_reloadedItems;
}
- (instancetype)init
{
self = [super init];
if (self) {
_deletedSections = [NSMutableIndexSet new];
_insertedSections = [NSMutableIndexSet new];
_reloadedSections = [NSMutableIndexSet new];
_deletedItems = [NSMutableArray new];
_insertedItems = [NSMutableArray new];
_reloadedItems = [NSMutableArray new];
_insertItemChanges = [NSMutableArray new];
_deleteItemChanges = [NSMutableArray new];
_reloadItemChanges = [NSMutableArray new];
_insertSectionChanges = [NSMutableArray new];
_deleteSectionChanges = [NSMutableArray new];
_reloadSectionChanges = [NSMutableArray new];
}
return self;
}
#pragma mark External API
- (void)markCompleted
{
NSAssert(!_completed, @"Attempt to mark already-completed changeset as completed.");
_completed = YES;
[self _sortAndCoalesceChangeArrays];
}
- (NSArray *)sectionChangesOfType:(_ASHierarchyChangeType)changeType
{
[self _ensureCompleted];
switch (changeType) {
case _ASHierarchyChangeTypeInsert:
return _insertSectionChanges;
case _ASHierarchyChangeTypeReload:
return _reloadSectionChanges;
case _ASHierarchyChangeTypeDelete:
return _deleteSectionChanges;
default:
NSAssert(NO, @"Request for section changes with invalid type: %lu", changeType);
}
}
- (NSArray *)itemChangesOfType:(_ASHierarchyChangeType)changeType
{
[self _ensureCompleted];
switch (changeType) {
case _ASHierarchyChangeTypeInsert:
return _insertItemChanges;
case _ASHierarchyChangeTypeReload:
return _reloadItemChanges;
case _ASHierarchyChangeTypeDelete:
return _deleteItemChanges;
default:
NSAssert(NO, @"Request for item changes with invalid type: %lu", changeType);
}
}
- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
{
[self _ensureNotCompleted];
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexPaths:indexPaths animationOptions:options presorted:NO];
[_deleteItemChanges addObject:change];
}
- (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
{
[self _ensureNotCompleted];
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexSet:sections animationOptions:options];
[_deleteSectionChanges addObject:change];
}
- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
{
[self _ensureNotCompleted];
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexPaths:indexPaths animationOptions:options presorted:NO];
[_insertItemChanges addObject:change];
}
- (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
{
[self _ensureNotCompleted];
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexSet:sections animationOptions:options];
[_insertSectionChanges addObject:change];
}
- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
{
[self _ensureNotCompleted];
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexPaths:indexPaths animationOptions:options presorted:NO];
[_reloadItemChanges addObject:change];
}
- (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
{
[self _ensureNotCompleted];
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexSet:sections animationOptions:options];
[_reloadSectionChanges addObject:change];
}
#pragma mark Private
- (BOOL)_ensureNotCompleted
{
NSAssert(!_completed, @"Attempt to modify completed changeset %@", self);
return !_completed;
}
- (BOOL)_ensureCompleted
{
NSAssert(_completed, @"Attempt to process incomplete changeset %@", self);
return _completed;
}
- (void)_sortAndCoalesceChangeArrays
{
@autoreleasepool {
[_ASHierarchySectionChange sortAndCoalesceChanges:_deleteSectionChanges];
[_ASHierarchySectionChange sortAndCoalesceChanges:_insertSectionChanges];
[_ASHierarchySectionChange sortAndCoalesceChanges:_reloadSectionChanges];
[_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:_deletedSections];
[_ASHierarchyItemChange sortAndCoalesceChanges:_reloadItemChanges ignoringChangesInSections:_reloadedSections];
[_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:_insertedSections];
}
}
@end
@implementation _ASHierarchySectionChange
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions
{
self = [super init];
if (self) {
_changeType = changeType;
_indexSet = indexSet;
_animationOptions = animationOptions;
}
return self;
}
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes
{
if (changes.count < 1) {
return;
}
_ASHierarchyChangeType type = [changes.firstObject changeType];
// Lookup table [Int: AnimationOptions]
NSMutableDictionary *animationOptions = [NSMutableDictionary new];
// All changed indexes, sorted
NSMutableIndexSet *allIndexes = [NSMutableIndexSet new];
for (_ASHierarchySectionChange *change in changes) {
[change.indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, __unused BOOL *stop) {
animationOptions[@(idx)] = @(change.animationOptions);
}];
[allIndexes addIndexes:change.indexSet];
}
// Create new changes by grouping sorted changes by animation option
NSMutableArray *result = [NSMutableArray new];
__block ASDataControllerAnimationOptions currentOptions = 0;
__block NSMutableIndexSet *currentIndexes = nil;
NSUInteger lastIndex = allIndexes.lastIndex;
NSEnumerationOptions options = type == _ASHierarchyChangeTypeDelete ? NSEnumerationReverse : kNilOptions;
[allIndexes enumerateIndexesWithOptions:options usingBlock:^(NSUInteger idx, __unused BOOL * stop) {
ASDataControllerAnimationOptions options = [animationOptions[@(idx)] integerValue];
BOOL endingCurrentGroup = NO;
if (currentIndexes == nil) {
// Starting a new group
currentIndexes = [NSMutableIndexSet indexSetWithIndex:idx];
currentOptions = options;
} else if (options == currentOptions) {
// Continuing the current group
[currentIndexes addIndex:idx];
} else {
endingCurrentGroup = YES;
}
BOOL endingLastGroup = (currentIndexes != nil && lastIndex == idx);
if (endingCurrentGroup || endingLastGroup) {
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:currentIndexes animationOptions:currentOptions];
[result addObject:change];
currentOptions = 0;
currentIndexes = nil;
}
}];
[changes setArray:result];
}
@end
@implementation _ASHierarchyItemChange
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted
{
self = [super init];
if (self) {
_changeType = changeType;
if (presorted) {
_indexPaths = indexPaths;
} else {
SEL sorting = changeType == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:);
_indexPaths = [indexPaths sortedArrayUsingSelector:sorting];
}
_animationOptions = animationOptions;
}
return self;
}
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSections:(NSIndexSet *)sections
{
if (changes.count < 1) {
return;
}
_ASHierarchyChangeType type = [changes.firstObject changeType];
// Lookup table [NSIndexPath: AnimationOptions]
NSMutableDictionary *animationOptions = [NSMutableDictionary new];
// All changed index paths, sorted
NSMutableArray *allIndexPaths = [NSMutableArray new];
NSPredicate *indexPathInValidSection = [NSPredicate predicateWithBlock:^BOOL(NSIndexPath *indexPath, __unused NSDictionary *_) {
return ![sections containsIndex:indexPath.section];
}];
for (_ASHierarchyItemChange *change in changes) {
for (NSIndexPath *indexPath in change.indexPaths) {
if ([indexPathInValidSection evaluateWithObject:indexPath]) {
animationOptions[indexPath] = @(change.animationOptions);
[allIndexPaths addObject:indexPath];
}
}
}
SEL sorting = type == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:);
[allIndexPaths sortUsingSelector:sorting];
// Create new changes by grouping sorted changes by animation option
NSMutableArray *result = [NSMutableArray new];
ASDataControllerAnimationOptions currentOptions = 0;
NSMutableArray *currentIndexPaths = nil;
NSIndexPath *lastIndexPath = allIndexPaths.lastObject;
for (NSIndexPath *indexPath in allIndexPaths) {
ASDataControllerAnimationOptions options = [animationOptions[indexPath] integerValue];
BOOL endingCurrentGroup = NO;
if (currentIndexPaths == nil) {
// Starting a new group
currentIndexPaths = [NSMutableArray arrayWithObject:indexPath];
currentOptions = options;
} else if (options == currentOptions) {
// Continuing the current group
[currentIndexPaths addObject:indexPath];
} else {
endingCurrentGroup = YES;
}
BOOL endingLastGroup = (currentIndexPaths != nil && (NSOrderedSame == [lastIndexPath compare:indexPath]));
if (endingCurrentGroup || endingLastGroup) {
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:currentIndexPaths animationOptions:currentOptions presorted:YES];
[result addObject:change];
currentOptions = 0;
currentIndexPaths = nil;
}
}
[changes setArray:result];
}
@end

View File

@@ -37,7 +37,6 @@
CGFloat borderWidth;
CGColorRef borderColor;
BOOL asyncTransactionContainer;
NSString *name;
BOOL isAccessibilityElement;
NSString *accessibilityLabel;
NSString *accessibilityHint;
@@ -85,7 +84,6 @@
int setBorderWidth:1;
int setBorderColor:1;
int setAsyncTransactionContainer:1;
int setName:1;
int setAllowsEdgeAntialiasing:1;
int setEdgeAntialiasingMask:1;
int setIsAccessibilityElement:1;
@@ -133,7 +131,6 @@
@synthesize borderWidth=borderWidth;
@synthesize borderColor=borderColor;
@synthesize asyncdisplaykit_asyncTransactionContainer=asyncTransactionContainer;
@synthesize asyncdisplaykit_name=name;
- (id)init
{
@@ -419,20 +416,6 @@
_flags.setAsyncTransactionContainer = YES;
}
// This is named this way, since I'm not sure we can change the setter for the CA version
- (void)setAsyncdisplaykit_name:(NSString *)newName
{
_flags.setName = YES;
if (name != newName) {
name = [newName copy];
}
}
- (NSString *)asyncdisplaykit_name
{
return name;
}
- (BOOL)isAccessibilityElement
{
return isAccessibilityElement;
@@ -641,9 +624,6 @@
if (_flags.setAsyncTransactionContainer)
layer.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer;
if (_flags.setName)
layer.asyncdisplaykit_name = name;
if (_flags.setOpaque)
ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired");
}
@@ -756,9 +736,6 @@
if (_flags.setAsyncTransactionContainer)
view.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer;
if (_flags.setName)
layer.asyncdisplaykit_name = name;
if (_flags.setOpaque)
ASDisplayNodeAssert(view.layer.opaque == opaque, @"Didn't set opaque as desired");
@@ -1004,4 +981,11 @@
return pendingState;
}
- (void)dealloc
{
CGColorRelease(backgroundColor);
CGColorRelease(shadowColor);
CGColorRelease(borderColor);
}
@end

View File

@@ -0,0 +1,368 @@
/* Copyright (c) 2015-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 <XCTest/XCTest.h>
#import <UIKit/UIKit.h>
#import "ASCollectionView.h"
#import "ASCollectionViewFlowLayoutInspector.h"
/**
* Test Data Source
*/
@interface InspectorTestDataSource : NSObject <ASCollectionViewDataSource>
@end
@implementation InspectorTestDataSource
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return [[ASCellNode alloc] init];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 0;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 2;
}
@end
@interface ASCollectionViewFlowLayoutInspectorTests : XCTestCase
@end
/**
* Test Delegate for Header Reference Size Implementation
*/
@interface HeaderReferenceSizeTestDelegate : NSObject <ASCollectionViewDelegateFlowLayout>
@end
@implementation HeaderReferenceSizeTestDelegate
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
return CGSizeMake(125.0, 125.0);
}
@end
/**
* Test Delegate for Footer Reference Size Implementation
*/
@interface FooterReferenceSizeTestDelegate : NSObject <ASCollectionViewDelegateFlowLayout>
@end
@implementation FooterReferenceSizeTestDelegate
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
{
return CGSizeMake(125.0, 125.0);
}
@end
@implementation ASCollectionViewFlowLayoutInspectorTests
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
#pragma mark - #collectionView:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:
// Vertical
// Delegate implementation
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheHeaderDelegateImplementation
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheFooterDelegateImplementation
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
FooterReferenceSizeTestDelegate *delegate = [[FooterReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
// Size implementation
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheHeaderProperty
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.headerReferenceSize = CGSizeMake(125.0, 125.0);
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheFooterProperty
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
// Horizontal
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheHeaderDelegateImplementation
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.height));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheFooterDelegateImplementation
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
FooterReferenceSizeTestDelegate *delegate = [[FooterReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.height));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
// Size implementation
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheHeaderProperty
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.headerReferenceSize = CGSizeMake(125.0, 125.0);
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.width));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheFooterProperty
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.height));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsZeroSizeWhenNoReferenceSizeIsImplemented
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeZero);
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a zero size");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
#pragma mark - #collectionView:numberOfSectionsForSupplementaryNodeOfKind:
- (void)testThatItRespondsWithTheDefaultNumberOfSections
{
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger sections = [inspector collectionView:collectionView numberOfSectionsForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
XCTAssert(sections == 1, @"should return 1 by default");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItProvidesTheNumberOfSectionsInTheDataSource
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger sections = [inspector collectionView:collectionView numberOfSectionsForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
XCTAssert(sections == 2, @"should return 2");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
#pragma mark - #collectionView:supplementaryNodesOfKind:inSection:
- (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheDelegate
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionHeader inSection:0];
XCTAssert(count == 1, @"should have a header supplementary view");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheLayout
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0];
XCTAssert(count == 1, @"should have a footer supplementary view");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsNoneWhenNoReferenceSizeIsImplemented
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0];
XCTAssert(count == 0, @"should not have a footer supplementary view");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
@end

View File

@@ -6,7 +6,9 @@
//
#import <XCTest/XCTest.h>
#import <AsyncDisplayKit/ASCollectionView.h>
#import "ASCollectionView.h"
#import "ASCollectionDataController.h"
#import "ASCollectionViewFlowLayoutInspector.h"
@interface ASCollectionViewTestDelegate : NSObject <ASCollectionViewDataSource, ASCollectionViewDelegate>
@@ -73,13 +75,43 @@
@end
@interface ASCollectionView (InternalTesting)
- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController;
@end
@interface ASCollectionViewTests : XCTestCase
@end
@implementation ASCollectionViewTests
- (void)DISABLED_testCollectionViewController {
- (void)testThatItSetsALayoutInspectorForFlowLayouts
{
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
XCTAssert(collectionView.layoutInspector != nil, @"should automatically set a layout delegate for flow layouts");
XCTAssert([collectionView.layoutInspector isKindOfClass:[ASCollectionViewFlowLayoutInspector class]], @"should have a flow layout inspector by default");
}
- (void)testThatItDoesNotSetALayoutInspectorForCustomLayouts
{
UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
XCTAssert(collectionView.layoutInspector == nil, @"should not set a layout delegate for custom layouts");
}
- (void)testThatRegisteringASupplementaryNodeStoresItForIntrospection
{
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
[collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
XCTAssertEqualObjects([collectionView supplementaryNodeKindsInDataController:nil], @[UICollectionElementKindSectionHeader]);
}
- (void)DISABLED_testCollectionViewController
{
ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil];
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];

View File

@@ -56,9 +56,15 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
@interface ASDisplayNodeAppearanceTests : XCTestCase
@end
// Conveniences for making nodes named a certain way
#define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.name = @#n
#define DeclareViewNamed(v) UIView *v = [[UIView alloc] init]; v.layer.asyncdisplaykit_name = @#v
#define DeclareLayerNamed(l) CALayer *l = [[CALayer alloc] init]; l.asyncdisplaykit_name = @#l
#define DeclareViewNamed(v) UIView *v = viewWithName(@#v)
static UIView *viewWithName(NSString *name) {
ASDisplayNode *n = [[ASDisplayNode alloc] init];
n.name = name;
return n.view;
}
@implementation ASDisplayNodeAppearanceTests
{

View File

@@ -17,21 +17,33 @@
#import "UIView+ASConvenience.h"
// Conveniences for making nodes named a certain way
#define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.name = @#n
#define DeclareViewNamed(v) UIView *v = [[UIView alloc] init]; v.layer.asyncdisplaykit_name = @#v
#define DeclareLayerNamed(l) CALayer *l = [[CALayer alloc] init]; l.asyncdisplaykit_name = @#l
#define DeclareViewNamed(v) UIView *v = viewWithName(@#v)
#define DeclareLayerNamed(l) CALayer *l = layerWithName(@#l)
static UIView *viewWithName(NSString *name) {
ASDisplayNode *n = [[ASDisplayNode alloc] init];
n.name = name;
return n.view;
}
static CALayer *layerWithName(NSString *name) {
ASDisplayNode *n = [[ASDisplayNode alloc] init];
n.layerBacked = YES;
n.name = name;
return n.layer;
}
static NSString *orderStringFromSublayers(CALayer *l) {
return [[[l.sublayers valueForKey:@"asyncdisplaykit_name"] allObjects] componentsJoinedByString:@","];
return [[l.sublayers valueForKey:@"name"] componentsJoinedByString:@","];
}
static NSString *orderStringFromSubviews(UIView *v) {
return [[[v.subviews valueForKeyPath:@"layer.asyncdisplaykit_name"] allObjects] componentsJoinedByString:@","];
return [[v.subviews valueForKey:@"name"] componentsJoinedByString:@","];
}
static NSString *orderStringFromSubnodes(ASDisplayNode *n) {
return [[[n.subnodes valueForKey:@"name"] allObjects] componentsJoinedByString:@","];
return [[n.subnodes valueForKey:@"name"] componentsJoinedByString:@","];
}
// Asserts subnode, subview, sublayer order match what you provide here
@@ -1375,7 +1387,6 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
DeclareNodeNamed(b);
DeclareNodeNamed(c);
DeclareViewNamed(d);
DeclareLayerNamed(e);
[parent layer];

View File

@@ -0,0 +1,85 @@
//
// ASStaticLayoutSpecSnapshotTests.m
// AsyncDisplayKit
//
// Created by Huy Nguyen on 18/10/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import "ASLayoutSpecSnapshotTestsHelper.h"
#import "ASStaticLayoutSpec.h"
#import "ASBackgroundLayoutSpec.h"
@interface ASStaticLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
@end
@implementation ASStaticLayoutSpecSnapshotTests
- (void)setUp
{
[super setUp];
self.recordMode = NO;
}
- (void)testSizingBehaviour
{
[self testWithSizeRange:ASSizeRangeMake(CGSizeMake(150, 200), CGSizeMake(FLT_MAX, FLT_MAX))
identifier:@"underflowChildren"];
[self testWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(50, 100))
identifier:@"overflowChildren"];
// Expect the spec to wrap its content because children sizes are between constrained size
[self testWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(FLT_MAX / 2, FLT_MAX / 2))
identifier:@"wrappedChildren"];
}
- (void)testChildrenMeasuredWithAutoMaxSize
{
ASStaticSizeDisplayNode *firstChild = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
firstChild.layoutPosition = CGPointMake(0, 0);
firstChild.staticSize = CGSizeMake(50, 50);
ASStaticSizeDisplayNode *secondChild = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
secondChild.layoutPosition = CGPointMake(10, 60);
secondChild.staticSize = CGSizeMake(100, 100);
ASSizeRange sizeRange = ASSizeRangeMake(CGSizeMake(10, 10), CGSizeMake(110, 160));
[self testWithChildren:@[firstChild, secondChild] sizeRange:sizeRange identifier:nil];
XCTAssertTrue(ASSizeRangeEqualToSizeRange(firstChild.constrainedSizeForCalculatedLayout,
ASSizeRangeMake(CGSizeZero, sizeRange.max)));
CGSize secondChildMaxSize = CGSizeMake(sizeRange.max.width - secondChild.layoutPosition.x,
sizeRange.max.height - secondChild.layoutPosition.y);
XCTAssertTrue(ASSizeRangeEqualToSizeRange(secondChild.constrainedSizeForCalculatedLayout,
ASSizeRangeMake(CGSizeZero, secondChildMaxSize)));
}
- (void)testWithSizeRange:(ASSizeRange)sizeRange identifier:(NSString *)identifier
{
ASDisplayNode *firstChild = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
firstChild.layoutPosition = CGPointMake(0, 0);
firstChild.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(CGSizeMake(50, 50));
ASDisplayNode *secondChild = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
secondChild.layoutPosition = CGPointMake(0, 50);
secondChild.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(CGSizeMake(100, 100));
[self testWithChildren:@[firstChild, secondChild] sizeRange:sizeRange identifier:identifier];
}
- (void)testWithChildren:(NSArray *)children sizeRange:(ASSizeRange)sizeRange identifier:(NSString *)identifier
{
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor whiteColor]);
NSMutableArray *subnodes = [NSMutableArray arrayWithArray:children];
[subnodes insertObject:backgroundNode atIndex:0];
ASStaticLayoutSpec *staticLayoutSpec = [ASStaticLayoutSpec staticLayoutSpecWithChildren:children];
ASLayoutSpec *layoutSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:staticLayoutSpec
background:backgroundNode];
[self testLayoutSpec:layoutSpec sizeRange:sizeRange subnodes:subnodes identifier:identifier];
}
@end

View File

@@ -9,18 +9,44 @@
#import <XCTest/XCTest.h>
#import "ASTableView.h"
#import "ASTableViewInternal.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASChangeSetDataController.h"
#define NumberOfSections 10
#define NumberOfRowsPerSection 20
#define NumberOfReloadIterations 50
@interface ASTestDataController : ASChangeSetDataController
@property (atomic) int numberOfAllNodesRelayouts;
@end
@implementation ASTestDataController
- (void)relayoutAllNodes
{
_numberOfAllNodesRelayouts++;
[super relayoutAllNodes];
}
@end
@interface ASTestTableView : ASTableView
@property (atomic, copy) void (^willDeallocBlock)(ASTableView *tableView);
@end
@implementation ASTestTableView
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled
{
return [super initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] asyncDataFetching:asyncDataFetchingEnabled];
}
- (ASTestDataController *)testDataController
{
return (ASTestDataController *)self.dataController;
}
- (void)dealloc
{
if (_willDeallocBlock) {
@@ -219,7 +245,7 @@
}
}
- (void)testRelayoutAllRowsWithNonZeroSizeInitially
- (void)testRelayoutAllNodesWithNonZeroSizeInitially
{
// Initial width of the table view is non-zero and all nodes are measured with this size.
// Any subsequence size change must trigger a relayout.
@@ -233,14 +259,14 @@
tableView.asyncDelegate = dataSource;
tableView.asyncDataSource = dataSource;
[tableView layoutIfNeeded];
// Trigger layout measurement on all nodes
[tableView reloadData];
[self triggerSizeChangeAndAssertRelayoutAllRowsForTableView:tableView newSize:tableViewFinalSize];
XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 0);
[self triggerSizeChangeAndAssertRelayoutAllNodesForTableView:tableView newSize:tableViewFinalSize];
}
- (void)testRelayoutAllRowsWithZeroSizeInitially
- (void)testRelayoutAllNodesWithZeroSizeInitially
{
// Initial width of the table view is 0. The first size change is part of the initial config.
// Any subsequence size change after that must trigger a relayout.
@@ -258,40 +284,10 @@
[superview addSubview:tableView];
// Width and height are swapped so that a later size change will simulate a rotation
tableView.frame = CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width);
// Trigger layout measurement on all nodes
[tableView layoutIfNeeded];
[self triggerSizeChangeAndAssertRelayoutAllRowsForTableView:tableView newSize:tableViewFinalSize];
}
- (void)triggerSizeChangeAndAssertRelayoutAllRowsForTableView:(ASTableView *)tableView newSize:(CGSize)newSize
{
XCTestExpectation *nodesMeasuredUsingNewConstrainedSizeExpectation = [self expectationWithDescription:@"nodesMeasuredUsingNewConstrainedSize"];
[tableView beginUpdates];
CGRect frame = tableView.frame;
frame.size = newSize;
tableView.frame = frame;
[tableView layoutIfNeeded];
[tableView endUpdatesAnimated:NO completion:^(BOOL completed) {
for (int section = 0; section < NumberOfSections; section++) {
for (int row = 0; row < NumberOfRowsPerSection; row++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath];
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 1);
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, newSize.width);
}
}
[nodesMeasuredUsingNewConstrainedSizeExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
if (error) {
XCTFail(@"Expectation failed: %@", error);
}
}];
XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 0);
[self triggerSizeChangeAndAssertRelayoutAllNodesForTableView:tableView newSize:tableViewFinalSize];
}
- (void)testRelayoutVisibleRowsWhenEditingModeIsChanged
@@ -304,24 +300,8 @@
tableView.asyncDelegate = dataSource;
tableView.asyncDataSource = dataSource;
XCTestExpectation *reloadDataExpectation = [self expectationWithDescription:@"reloadData"];
[tableView reloadDataWithCompletion:^{
for (int section = 0; section < NumberOfSections; section++) {
for (int row = 0; row < NumberOfRowsPerSection; row++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath];
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 0);
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width);
}
}
[reloadDataExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
if (error) {
XCTFail(@"Expectation failed: %@", error);
}
}];
[self triggerFirstLayoutMeasurementForTableView:tableView];
NSArray *visibleNodes = [tableView visibleNodes];
XCTAssertGreaterThan(visibleNodes.count, 0);
@@ -390,23 +370,7 @@
tableView.asyncDelegate = dataSource;
tableView.asyncDataSource = dataSource;
XCTestExpectation *reloadDataExpectation = [self expectationWithDescription:@"reloadData"];
[tableView reloadDataWithCompletion:^{
for (int section = 0; section < NumberOfSections; section++) {
for (int row = 0; row < NumberOfRowsPerSection; row++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath];
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 0);
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width);
}
}
[reloadDataExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
if (error) {
XCTFail(@"Expectation failed: %@", error);
}
}];
[self triggerFirstLayoutMeasurementForTableView:tableView];
// Cause table view to enter editing mode and then scroll to the bottom.
// The last node should be re-measured on main thread with the new (smaller) content view width.
@@ -451,4 +415,55 @@
}];
}
- (void)triggerFirstLayoutMeasurementForTableView:(ASTableView *)tableView{
XCTestExpectation *reloadDataExpectation = [self expectationWithDescription:@"reloadData"];
[tableView reloadDataWithCompletion:^{
for (int section = 0; section < NumberOfSections; section++) {
for (int row = 0; row < NumberOfRowsPerSection; row++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath];
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 0);
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, tableView.frame.size.width);
}
}
[reloadDataExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
if (error) {
XCTFail(@"Expectation failed: %@", error);
}
}];
}
- (void)triggerSizeChangeAndAssertRelayoutAllNodesForTableView:(ASTestTableView *)tableView newSize:(CGSize)newSize
{
XCTestExpectation *nodesMeasuredUsingNewConstrainedSizeExpectation = [self expectationWithDescription:@"nodesMeasuredUsingNewConstrainedSize"];
[tableView beginUpdates];
CGRect frame = tableView.frame;
frame.size = newSize;
tableView.frame = frame;
[tableView layoutIfNeeded];
[tableView endUpdatesAnimated:NO completion:^(BOOL completed) {
XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 1);
for (int section = 0; section < NumberOfSections; section++) {
for (int row = 0; row < NumberOfRowsPerSection; row++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath];
XCTAssertLessThanOrEqual(node.numberOfLayoutsOnMainThread, 1);
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, newSize.width);
}
}
[nodesMeasuredUsingNewConstrainedSizeExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
if (error) {
XCTFail(@"Expectation failed: %@", error);
}
}];
}
@end

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -80,7 +80,7 @@ to implement node hierarchies or custom drawing.
* Read the [Getting Started guide](http://asyncdisplaykit.org/guide/)
* Get the [sample projects](https://github.com/facebook/AsyncDisplayKit/tree/master/examples)
* Browse the [API reference](http://asyncdisplaykit.org/appledoc/)
* Watch the [NSLondon talk](http://vimeo.com/103589245)
* Watch the [NSLondon talk](http://vimeo.com/103589245) or the [NSSpain talk](https://www.youtube.com/watch?v=RY_X7l1g79Q)
## Testing

View File

@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */; settings = {ASSET_TAGS = (); }; };
9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; settings = {ASSET_TAGS = (); }; };
AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; };
AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; };
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; };
@@ -16,6 +18,9 @@
/* Begin PBXFileReference section */
2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; };
9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SupplementaryNode.h; sourceTree = "<group>"; };
9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SupplementaryNode.m; sourceTree = "<group>"; };
9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Launchboard.storyboard; sourceTree = "<group>"; };
AC3C4A5E1A11F47200143C57 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
AC3C4A621A11F47200143C57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
AC3C4A631A11F47200143C57 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@@ -76,6 +81,8 @@
AC3C4A691A11F47200143C57 /* ViewController.m */,
AC3C4A8D1A11F80C00143C57 /* Images.xcassets */,
AC3C4A611A11F47200143C57 /* Supporting Files */,
9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */,
9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */,
);
indentWidth = 2;
path = Sample;
@@ -88,6 +95,7 @@
children = (
AC3C4A621A11F47200143C57 /* Info.plist */,
AC3C4A631A11F47200143C57 /* main.m */,
9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */,
);
name = "Supporting Files";
sourceTree = "<group>";
@@ -159,6 +167,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */,
AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -204,6 +213,7 @@
buildActionMask = 2147483647;
files = (
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */,
9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */,
AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */,
AC3C4A641A11F47200143C57 /* main.m in Sources */,
);

View File

@@ -26,6 +26,8 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>Launchboard</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
</dependencies>
<scenes/>
</document>

View File

@@ -0,0 +1,18 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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.
*/
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface SupplementaryNode : ASCellNode
- (instancetype)initWithText:(NSString *)text;
@end

View File

@@ -0,0 +1,55 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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.
*/
#import "SupplementaryNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
static CGFloat kInsets = 15.0;
@implementation SupplementaryNode {
ASTextNode *_textNode;
}
- (instancetype)initWithText:(NSString *)text
{
self = [super init];
if (self != nil) {
_textNode = [[ASTextNode alloc] init];
_textNode.attributedString = [[NSAttributedString alloc] initWithString:text
attributes:[self textAttributes]];
[self addSubnode:_textNode];
}
return self;
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASCenterLayoutSpec *center = [[ASCenterLayoutSpec alloc] init];
center.centeringOptions = ASCenterLayoutSpecCenteringXY;
center.child = _textNode;
UIEdgeInsets insets = UIEdgeInsetsMake(kInsets, kInsets, kInsets, kInsets);
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:center];
}
#pragma mark - Text Formatting
- (NSDictionary *)textAttributes
{
return @{
NSFontAttributeName: [UIFont systemFontOfSize:18.0],
NSForegroundColorAttributeName: [UIColor whiteColor],
};
}
@end

View File

@@ -12,8 +12,9 @@
#import "ViewController.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import "SupplementaryNode.h"
@interface ViewController () <ASCollectionViewDataSource, ASCollectionViewDelegate>
@interface ViewController () <ASCollectionViewDataSource, ASCollectionViewDelegateFlowLayout>
{
ASCollectionView *_collectionView;
}
@@ -32,13 +33,17 @@
return nil;
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.headerReferenceSize = CGSizeMake(50.0, 50.0);
layout.footerReferenceSize = CGSizeMake(50.0, 50.0);
_collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:YES];
_collectionView.asyncDataSource = self;
_collectionView.asyncDelegate = self;
_collectionView.backgroundColor = [UIColor whiteColor];
[_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
[_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter];
return self;
}
@@ -73,9 +78,26 @@
return node;
}
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
NSString *text = [kind isEqualToString:UICollectionElementKindSectionHeader] ? @"Header" : @"Footer";
SupplementaryNode *node = [[SupplementaryNode alloc] initWithText:text];
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
node.backgroundColor = [UIColor blueColor];
} else {
node.backgroundColor = [UIColor redColor];
}
return node;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 300;
return 10;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 100;
}
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView
@@ -95,7 +117,7 @@
[context completeBatchFetching:YES];
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(20.0, 20.0, 20.0, 20.0);
}

View File

@@ -185,13 +185,22 @@ static const CGFloat kInnerPadding = 10.0f;
- (void)toggleImageEnlargement
{
_isImageEnlarged = !_isImageEnlarged;
[self setNeedsLayout];
}
- (void)toggleNodesSwap
{
_swappedTextAndImage = !_swappedTextAndImage;
[self setNeedsLayout];
[UIView animateWithDuration:0.15 animations:^{
self.alpha = 0;
} completion:^(BOOL finished) {
[self setNeedsLayout];
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.15 animations:^{
self.alpha = 1;
}];
}];
}
@end

View File

@@ -118,11 +118,10 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[_tableView deselectRowAtIndexPath:indexPath animated:YES];
[_tableView beginUpdates];
// Assume only kitten nodes are selectable (see -tableView:shouldHighlightRowAtIndexPath:).
KittenNode *node = (KittenNode *)[_tableView nodeForRowAtIndexPath:indexPath];
[node toggleImageEnlargement];
[_tableView endUpdates];
[_tableView relayoutRowAtIndexPath:indexPath withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,3 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
pod 'AsyncDisplayKit', :path => '../..'

View File

@@ -0,0 +1,377 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
05561CFA19D4E77700CBA93C /* BlurbNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 05561CF919D4E77700CBA93C /* BlurbNode.m */; };
05561CFD19D4F94A00CBA93C /* KittenNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05561CFC19D4F94A00CBA93C /* KittenNode.mm */; };
0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; };
05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; };
05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; };
05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; };
3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */; };
6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; };
6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
05561CF819D4E77700CBA93C /* BlurbNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlurbNode.h; sourceTree = "<group>"; };
05561CF919D4E77700CBA93C /* BlurbNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlurbNode.m; sourceTree = "<group>"; };
05561CFB19D4F94A00CBA93C /* KittenNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KittenNode.h; sourceTree = "<group>"; };
05561CFC19D4F94A00CBA93C /* KittenNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KittenNode.mm; sourceTree = "<group>"; };
0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = "<group>"; };
05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; };
3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; };
6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; };
C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
05E2127E19D4DB510098F589 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
05E2127819D4DB510098F589 = {
isa = PBXGroup;
children = (
05E2128319D4DB510098F589 /* Sample */,
05E2128219D4DB510098F589 /* Products */,
1A943BF0259746F18D6E423F /* Frameworks */,
1AE410B73DA5C3BD087ACDD7 /* Pods */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
05E2128219D4DB510098F589 /* Products */ = {
isa = PBXGroup;
children = (
05E2128119D4DB510098F589 /* Sample.app */,
);
name = Products;
sourceTree = "<group>";
};
05E2128319D4DB510098F589 /* Sample */ = {
isa = PBXGroup;
children = (
05E2128819D4DB510098F589 /* AppDelegate.h */,
05E2128919D4DB510098F589 /* AppDelegate.m */,
05E2128B19D4DB510098F589 /* ViewController.h */,
05E2128C19D4DB510098F589 /* ViewController.m */,
05561CFB19D4F94A00CBA93C /* KittenNode.h */,
05561CFC19D4F94A00CBA93C /* KittenNode.mm */,
05561CF819D4E77700CBA93C /* BlurbNode.h */,
05561CF919D4E77700CBA93C /* BlurbNode.m */,
05E2128419D4DB510098F589 /* Supporting Files */,
);
path = Sample;
sourceTree = "<group>";
};
05E2128419D4DB510098F589 /* Supporting Files */ = {
isa = PBXGroup;
children = (
0585427F19D4DBE100606EA6 /* Default-568h@2x.png */,
6C2C82AA19EE274300767484 /* Default-667h@2x.png */,
6C2C82AB19EE274300767484 /* Default-736h@3x.png */,
05E2128519D4DB510098F589 /* Info.plist */,
05E2128619D4DB510098F589 /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
1A943BF0259746F18D6E423F /* Frameworks */ = {
isa = PBXGroup;
children = (
3D24B17D1E4A4E7A9566C5E9 /* libPods.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
1AE410B73DA5C3BD087ACDD7 /* Pods */ = {
isa = PBXGroup;
children = (
C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */,
088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
05E2128019D4DB510098F589 /* Sample */ = {
isa = PBXNativeTarget;
buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */;
buildPhases = (
E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */,
05E2127D19D4DB510098F589 /* Sources */,
05E2127E19D4DB510098F589 /* Frameworks */,
05E2127F19D4DB510098F589 /* Resources */,
F012A6F39E0149F18F564F50 /* Copy Pods Resources */,
ACCB3408566E7626721EF2D5 /* Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Sample;
productName = Sample;
productReference = 05E2128119D4DB510098F589 /* Sample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
05E2127919D4DB510098F589 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0600;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
05E2128019D4DB510098F589 = {
CreatedOnToolsVersion = 6.0.1;
};
};
};
buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 05E2127819D4DB510098F589;
productRefGroup = 05E2128219D4DB510098F589 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
05E2128019D4DB510098F589 /* Sample */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
05E2127F19D4DB510098F589 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */,
6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */,
6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
ACCB3408566E7626721EF2D5 /* 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;
files = (
);
inputPaths = (
);
name = "Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
05E2127D19D4DB510098F589 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
05561CFD19D4F94A00CBA93C /* KittenNode.mm in Sources */,
05E2128D19D4DB510098F589 /* ViewController.m in Sources */,
05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */,
05561CFA19D4E77700CBA93C /* BlurbNode.m in Sources */,
05E2128719D4DB510098F589 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
05E212A219D4DB510098F589 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
05E212A319D4DB510098F589 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
05E212A519D4DB510098F589 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
05E212A619D4DB510098F589 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
05E212A219D4DB510098F589 /* Debug */,
05E212A319D4DB510098F589 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
05E212A519D4DB510098F589 /* Debug */,
05E212A619D4DB510098F589 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 05E2127919D4DB510098F589 /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Sample.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0620"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "05E2128019D4DB510098F589"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "05E2128019D4DB510098F589"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "05E2128019D4DB510098F589"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "05E2128019D4DB510098F589"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1 @@
<?xml version='1.0' encoding='UTF-8'?><Workspace version='1.0'><FileRef location='group:Sample.xcodeproj'/><FileRef location='group:Pods/Pods.xcodeproj'/></Workspace>

View File

@@ -0,0 +1,20 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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.
*/
#import <UIKit/UIKit.h>
#define UseAutomaticLayout 1
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

View File

@@ -0,0 +1,27 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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.
*/
#import "AppDelegate.h"
#import "ViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]];
[self.window makeKeyAndVisible];
return YES;
}
@end

View File

@@ -0,0 +1,19 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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.
*/
#import <AsyncDisplayKit/AsyncDisplayKit.h>
/**
* Simple node that displays a placekitten.com attribution.
*/
@interface BlurbNode : ASCellNode
@end

View File

@@ -0,0 +1,122 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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.
*/
#import "BlurbNode.h"
#import "AppDelegate.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
static CGFloat kTextPadding = 10.0f;
static NSString *kLinkAttributeName = @"PlaceKittenNodeLinkAttributeName";
@interface BlurbNode () <ASTextNodeDelegate>
{
ASTextNode *_textNode;
}
@end
@implementation BlurbNode
#pragma mark -
#pragma mark ASCellNode.
- (instancetype)init
{
if (!(self = [super init]))
return nil;
// create a text node
_textNode = [[ASTextNode alloc] init];
// configure the node to support tappable links
_textNode.delegate = self;
_textNode.userInteractionEnabled = YES;
_textNode.linkAttributeNames = @[ kLinkAttributeName ];
// generate an attributed string using the custom link attribute specified above
NSString *blurb = @"kittens courtesy placekitten.com \U0001F638";
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:blurb];
[string addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"HelveticaNeue-Light" size:16.0f] range:NSMakeRange(0, blurb.length)];
[string addAttributes:@{
kLinkAttributeName: [NSURL URLWithString:@"http://placekitten.com/"],
NSForegroundColorAttributeName: [UIColor grayColor],
NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot),
}
range:[blurb rangeOfString:@"placekitten.com"]];
_textNode.attributedString = string;
// add it as a subnode, and we're done
[self addSubnode:_textNode];
return self;
}
- (void)didLoad
{
// enable highlighting now that self.layer has loaded -- see ASHighlightOverlayLayer.h
self.layer.as_allowsHighlightDrawing = YES;
[super didLoad];
}
#if UseAutomaticLayout
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASCenterLayoutSpec *centerSpec = [[ASCenterLayoutSpec alloc] init];
centerSpec.centeringOptions = ASCenterLayoutSpecCenteringX;
centerSpec.sizingOptions = ASCenterLayoutSpecSizingOptionMinimumY;
centerSpec.child = _textNode;
UIEdgeInsets padding =UIEdgeInsetsMake(kTextPadding, kTextPadding, kTextPadding, kTextPadding);
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:padding child:centerSpec];
}
#else
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
// called on a background thread. custom nodes must call -measure: on their subnodes in -calculateSizeThatFits:
CGSize measuredSize = [_textNode measure:CGSizeMake(constrainedSize.width - 2 * kTextPadding,
constrainedSize.height - 2 * kTextPadding)];
return CGSizeMake(constrainedSize.width, measuredSize.height + 2 * kTextPadding);
}
- (void)layout
{
// called on the main thread. we'll use the stashed size from above, instead of blocking on text sizing
CGSize textNodeSize = _textNode.calculatedSize;
_textNode.frame = CGRectMake(roundf((self.calculatedSize.width - textNodeSize.width) / 2.0f),
kTextPadding,
textNodeSize.width,
textNodeSize.height);
}
#endif
#pragma mark -
#pragma mark ASTextNodeDelegate methods.
- (BOOL)textNode:(ASTextNode *)richTextNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point
{
// opt into link highlighting -- tap and hold the link to try it! must enable highlighting on a layer, see -didLoad
return YES;
}
- (void)textNode:(ASTextNode *)richTextNode tappedLinkAttribute:(NSString *)attribute value:(NSURL *)URL atPoint:(CGPoint)point textRange:(NSRange)textRange
{
// the node tapped a link, open it
[[UIApplication sharedApplication] openURL:URL];
}
@end

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,24 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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.
*/
#import <AsyncDisplayKit/AsyncDisplayKit.h>
/**
* Social media-style node that displays a kitten picture and a random length
* of lorem ipsum text. Uses a placekitten.com kitten of the specified size.
*/
@interface KittenNode : ASCellNode
- (instancetype)initWithKittenOfSize:(CGSize)size;
- (void)toggleImageEnlargement;
@end

View File

@@ -0,0 +1,197 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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.
*/
#import "KittenNode.h"
#import "AppDelegate.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
static const CGFloat kImageSize = 80.0f;
static const CGFloat kOuterPadding = 16.0f;
static const CGFloat kInnerPadding = 10.0f;
@interface KittenNode ()
{
CGSize _kittenSize;
ASNetworkImageNode *_imageNode;
ASTextNode *_textNode;
ASDisplayNode *_divider;
BOOL _isImageEnlarged;
BOOL _swappedTextAndImage;
}
@end
@implementation KittenNode
// lorem ipsum text courtesy https://kittyipsum.com/ <3
+ (NSArray *)placeholders
{
static NSArray *placeholders = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
placeholders = @[
@"Kitty ipsum dolor sit amet, purr sleep on your face lay down in your way biting, sniff tincidunt a etiam fluffy fur judging you stuck in a tree kittens.",
@"Lick tincidunt a biting eat the grass, egestas enim ut lick leap puking climb the curtains lick.",
@"Lick quis nunc toss the mousie vel, tortor pellentesque sunbathe orci turpis non tail flick suscipit sleep in the sink.",
@"Orci turpis litter box et stuck in a tree, egestas ac tempus et aliquam elit.",
@"Hairball iaculis dolor dolor neque, nibh adipiscing vehicula egestas dolor aliquam.",
@"Sunbathe fluffy fur tortor faucibus pharetra jump, enim jump on the table I don't like that food catnip toss the mousie scratched.",
@"Quis nunc nam sleep in the sink quis nunc purr faucibus, chase the red dot consectetur bat sagittis.",
@"Lick tail flick jump on the table stretching purr amet, rhoncus scratched jump on the table run.",
@"Suspendisse aliquam vulputate feed me sleep on your keyboard, rip the couch faucibus sleep on your keyboard tristique give me fish dolor.",
@"Rip the couch hiss attack your ankles biting pellentesque puking, enim suspendisse enim mauris a.",
@"Sollicitudin iaculis vestibulum toss the mousie biting attack your ankles, puking nunc jump adipiscing in viverra.",
@"Nam zzz amet neque, bat tincidunt a iaculis sniff hiss bibendum leap nibh.",
@"Chase the red dot enim puking chuf, tristique et egestas sniff sollicitudin pharetra enim ut mauris a.",
@"Sagittis scratched et lick, hairball leap attack adipiscing catnip tail flick iaculis lick.",
@"Neque neque sleep in the sink neque sleep on your face, climb the curtains chuf tail flick sniff tortor non.",
@"Ac etiam kittens claw toss the mousie jump, pellentesque rhoncus litter box give me fish adipiscing mauris a.",
@"Pharetra egestas sunbathe faucibus ac fluffy fur, hiss feed me give me fish accumsan.",
@"Tortor leap tristique accumsan rutrum sleep in the sink, amet sollicitudin adipiscing dolor chase the red dot.",
@"Knock over the lamp pharetra vehicula sleep on your face rhoncus, jump elit cras nec quis quis nunc nam.",
@"Sollicitudin feed me et ac in viverra catnip, nunc eat I don't like that food iaculis give me fish.",
];
});
return placeholders;
}
- (instancetype)initWithKittenOfSize:(CGSize)size
{
if (!(self = [super init]))
return nil;
_kittenSize = size;
// kitten image, with a solid background colour serving as placeholder
_imageNode = [[ASNetworkImageNode alloc] init];
_imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor();
_imageNode.URL = [NSURL URLWithString:[NSString stringWithFormat:@"https://placekitten.com/%zd/%zd",
(NSInteger)roundl(_kittenSize.width),
(NSInteger)roundl(_kittenSize.height)]];
// _imageNode.contentMode = UIViewContentModeCenter;
[_imageNode addTarget:self action:@selector(toggleNodesSwap) forControlEvents:ASControlNodeEventTouchUpInside];
[self addSubnode:_imageNode];
// lorem ipsum text, plus some nice styling
_textNode = [[ASTextNode alloc] init];
_textNode.attributedString = [[NSAttributedString alloc] initWithString:[self kittyIpsum]
attributes:[self textStyle]];
[self addSubnode:_textNode];
// hairline cell separator
_divider = [[ASDisplayNode alloc] init];
_divider.backgroundColor = [UIColor lightGrayColor];
[self addSubnode:_divider];
return self;
}
- (NSString *)kittyIpsum
{
NSArray *placeholders = [KittenNode placeholders];
u_int32_t ipsumCount = (u_int32_t)[placeholders count];
u_int32_t location = arc4random_uniform(ipsumCount);
u_int32_t length = arc4random_uniform(ipsumCount - location);
NSMutableString *string = [placeholders[location] mutableCopy];
for (u_int32_t i = location + 1; i < location + length; i++) {
[string appendString:(i % 2 == 0) ? @"\n" : @" "];
[string appendString:placeholders[i]];
}
return string;
}
- (NSDictionary *)textStyle
{
UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:12.0f];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
style.paragraphSpacing = 0.5 * font.lineHeight;
style.hyphenationFactor = 1.0;
return @{ NSFontAttributeName: font,
NSParagraphStyleAttributeName: style };
}
#if UseAutomaticLayout
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
_imageNode.preferredFrameSize = _isImageEnlarged ? CGSizeMake(2.0 * kImageSize, 2.0 * kImageSize) : CGSizeMake(kImageSize, kImageSize);
_textNode.flexShrink = YES;
ASStackLayoutSpec *stackSpec = [[ASStackLayoutSpec alloc] init];
stackSpec.direction = ASStackLayoutDirectionHorizontal;
stackSpec.spacing = kInnerPadding;
[stackSpec setChildren:!_swappedTextAndImage ? @[_imageNode, _textNode] : @[_textNode, _imageNode]];
ASInsetLayoutSpec *insetSpec = [[ASInsetLayoutSpec alloc] init];
insetSpec.insets = UIEdgeInsetsMake(kOuterPadding, kOuterPadding, kOuterPadding, kOuterPadding);
insetSpec.child = stackSpec;
return insetSpec;
}
// With box model, you don't need to override this method, unless you want to add custom logic.
- (void)layout
{
[super layout];
// Manually layout the divider.
CGFloat pixelHeight = 1.0f / [[UIScreen mainScreen] scale];
_divider.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, pixelHeight);
}
#else
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
CGSize imageSize = CGSizeMake(kImageSize, kImageSize);
CGSize textSize = [_textNode measure:CGSizeMake(constrainedSize.width - kImageSize - 2 * kOuterPadding - kInnerPadding,
constrainedSize.height)];
// ensure there's room for the text
CGFloat requiredHeight = MAX(textSize.height, imageSize.height);
return CGSizeMake(constrainedSize.width, requiredHeight + 2 * kOuterPadding);
}
- (void)layout
{
CGFloat pixelHeight = 1.0f / [[UIScreen mainScreen] scale];
_divider.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, pixelHeight);
_imageNode.frame = CGRectMake(kOuterPadding, kOuterPadding, kImageSize, kImageSize);
CGSize textSize = _textNode.calculatedSize;
_textNode.frame = CGRectMake(kOuterPadding + kImageSize + kInnerPadding, kOuterPadding, textSize.width, textSize.height);
}
#endif
- (void)toggleImageEnlargement
{
_isImageEnlarged = !_isImageEnlarged;
[self setNeedsLayout];
}
- (void)toggleNodesSwap
{
_swappedTextAndImage = !_swappedTextAndImage;
[self setNeedsLayout];
}
@end

View File

@@ -0,0 +1,16 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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.
*/
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end

View File

@@ -0,0 +1,216 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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.
*/
#import "ViewController.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import <AsyncDisplayKit/ASAssert.h>
#import "BlurbNode.h"
#import "KittenNode.h"
static const NSInteger kLitterSize = 20; // intial number of kitten cells in ASTableView
static const NSInteger kLitterBatchSize = 10; // number of kitten cells to add to ASTableView
static const NSInteger kMaxLitterSize = 100; // max number of kitten cells allowed in ASTableView
@interface ViewController () <ASTableViewDataSource, ASTableViewDelegate>
{
ASTableView *_tableView;
// array of boxed CGSizes corresponding to placekitten.com kittens
NSMutableArray *_kittenDataSource;
BOOL _dataSourceLocked;
NSIndexPath *_blurbNodeIndexPath;
}
@property (nonatomic, strong) NSMutableArray *kittenDataSource;
@property (atomic, assign) BOOL dataSourceLocked;
@end
@implementation ViewController
#pragma mark -
#pragma mark UIViewController.
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain asyncDataFetching:YES];
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone; // KittenNode has its own separator
_tableView.asyncDataSource = self;
_tableView.asyncDelegate = self;
// populate our "data source" with some random kittens
_kittenDataSource = [self createLitterWithSize:kLitterSize];
_blurbNodeIndexPath = [NSIndexPath indexPathForItem:0 inSection:0];
self.title = @"Kittens";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit
target:self
action:@selector(toggleEditingMode)];
return self;
}
- (NSMutableArray *)createLitterWithSize:(NSInteger)litterSize
{
NSMutableArray *kittens = [NSMutableArray arrayWithCapacity:litterSize];
for (NSInteger i = 0; i < litterSize; i++) {
// placekitten.com will return the same kitten picture if the same pixel height & width are requested,
// so generate kittens with different width & height values.
u_int32_t deltaX = arc4random_uniform(10) - 5;
u_int32_t deltaY = arc4random_uniform(10) - 5;
CGSize size = CGSizeMake(350 + 2 * deltaX, 350 + 4 * deltaY);
[kittens addObject:[NSValue valueWithCGSize:size]];
}
return kittens;
}
- (void)setKittenDataSource:(NSMutableArray *)kittenDataSource {
ASDisplayNodeAssert(!self.dataSourceLocked, @"Could not update data source when it is locked !");
_kittenDataSource = kittenDataSource;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:_tableView];
[_tableView reloadDataImmediately];
}
- (void)viewWillLayoutSubviews
{
_tableView.frame = self.view.bounds;
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
- (void)toggleEditingMode
{
[_tableView setEditing:!_tableView.editing animated:YES];
}
#pragma mark -
#pragma mark ASTableView.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[_tableView deselectRowAtIndexPath:indexPath animated:YES];
[_tableView beginUpdates];
// Assume only kitten nodes are selectable (see -tableView:shouldHighlightRowAtIndexPath:).
KittenNode *node = (KittenNode *)[_tableView nodeForRowAtIndexPath:indexPath];
[node toggleImageEnlargement];
[_tableView endUpdates];
}
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
{
// special-case the first row
if ([_blurbNodeIndexPath compare:indexPath] == NSOrderedSame) {
BlurbNode *node = [[BlurbNode alloc] init];
return node;
}
NSValue *size = _kittenDataSource[indexPath.row - 1];
KittenNode *node = [[KittenNode alloc] initWithKittenOfSize:size.CGSizeValue];
return node;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// blurb node + kLitterSize kitties
return 1 + _kittenDataSource.count;
}
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
{
// Enable selection for kitten nodes
return [_blurbNodeIndexPath compare:indexPath] != NSOrderedSame;
}
- (void)tableViewLockDataSource:(ASTableView *)tableView
{
self.dataSourceLocked = YES;
}
- (void)tableViewUnlockDataSource:(ASTableView *)tableView
{
self.dataSourceLocked = NO;
}
- (BOOL)shouldBatchFetchForTableView:(UITableView *)tableView
{
return _kittenDataSource.count < kMaxLitterSize;
}
- (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(), ^{
// populate a new array of random-sized kittens
NSArray *moarKittens = [self createLitterWithSize:kLitterBatchSize];
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
// find number of kittens in the data source and create their indexPaths
NSInteger existingRows = _kittenDataSource.count + 1;
for (NSInteger i = 0; i < moarKittens.count; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:existingRows + i inSection:0]];
}
// add new kittens to the data source & notify table of new indexpaths
[_kittenDataSource addObjectsFromArray:moarKittens];
[tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[context completeBatchFetching:YES];
NSLog(@"kittens added");
});
});
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Enable editing for Kitten nodes
return [_blurbNodeIndexPath compare:indexPath] != NSOrderedSame;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Assume only kitten nodes are editable (see -tableView:canEditRowAtIndexPath:).
[_kittenDataSource removeObjectAtIndex:indexPath.row - 1];
[_tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
@end

View File

@@ -0,0 +1,20 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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.
*/
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}