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
2
.gitignore
vendored
@@ -26,3 +26,5 @@ docs/.sass-cache
|
||||
*.lock
|
||||
|
||||
*.gcov
|
||||
*.gcno
|
||||
*.gcda
|
||||
|
||||
BIN
AsyncDisplayKit-Prefix.gcda
Normal 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/'
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -123,7 +123,6 @@ static const CGFloat kFontSize = 18.0f;
|
||||
_textNode.attributedString = [[NSAttributedString alloc] initWithString:_text
|
||||
attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kFontSize]}];
|
||||
|
||||
[self invalidateCalculatedLayout];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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];
|
||||
|
||||
31
AsyncDisplayKit/ASTableViewInternal.h
Normal 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
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
- (instancetype)initWithNode:(ASDisplayNode *)node
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
if (!(self = [super initWithNibName:nil bundle:nil])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
23
AsyncDisplayKit/Details/ASChangeSetDataController.h
Normal 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
|
||||
179
AsyncDisplayKit/Details/ASChangeSetDataController.m
Normal 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
|
||||
39
AsyncDisplayKit/Details/ASCollectionDataController.h
Normal 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
|
||||
217
AsyncDisplayKit/Details/ASCollectionDataController.mm
Normal 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
|
||||
@@ -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
|
||||
123
AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
159
AsyncDisplayKit/Details/ASDataController+Subclasses.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
59
AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.h
Normal 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
|
||||
|
||||
|
||||
185
AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -87,3 +87,14 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASRatioLayoutSpec (Debugging)
|
||||
|
||||
#pragma mark - ASLayoutableAsciiArtProtocol
|
||||
|
||||
- (NSString *)asciiArtName
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ (%.1f)", NSStringFromClass([self class]), self.ratio];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -85,3 +85,14 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASStaticLayoutSpec (Debugging)
|
||||
|
||||
#pragma mark - ASLayoutableAsciiArtProtocol
|
||||
|
||||
- (NSString *)debugBoxString
|
||||
{
|
||||
return [ASLayoutSpec asciiArtStringForChildren:self.children parentName:[self asciiArtName]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
72
AsyncDisplayKit/Private/_ASHierarchyChangeSet.h
Normal 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
|
||||
337
AsyncDisplayKit/Private/_ASHierarchyChangeSet.m
Normal 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
|
||||
@@ -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
|
||||
|
||||
368
AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m
Normal 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
|
||||
@@ -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)];
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
85
AsyncDisplayKitTests/ASStaticLayoutSpecSnapshotTests.m
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 834 B |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
BIN
ObjectiveC.gcno
BIN
QuartzCore.gcno
@@ -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
|
||||
|
||||
|
||||
@@ -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 */,
|
||||
);
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>Launchboard</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
|
||||
7
examples/ASCollectionView/Sample/Launchboard.storyboard
Normal 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>
|
||||
18
examples/ASCollectionView/Sample/SupplementaryNode.h
Normal 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
|
||||
55
examples/ASCollectionView/Sample/SupplementaryNode.m
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
examples/SynchronousKittens/Default-568h@2x.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
examples/SynchronousKittens/Default-667h@2x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
examples/SynchronousKittens/Default-736h@3x.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
3
examples/SynchronousKittens/Podfile
Normal file
@@ -0,0 +1,3 @@
|
||||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
platform :ios, '8.0'
|
||||
pod 'AsyncDisplayKit', :path => '../..'
|
||||
377
examples/SynchronousKittens/Sample.xcodeproj/project.pbxproj
Normal 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 */;
|
||||
}
|
||||
7
examples/SynchronousKittens/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:Sample.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -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>
|
||||
1
examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata
generated
Normal 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>
|
||||
20
examples/SynchronousKittens/Sample/AppDelegate.h
Normal 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
|
||||
27
examples/SynchronousKittens/Sample/AppDelegate.m
Normal 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
|
||||
19
examples/SynchronousKittens/Sample/BlurbNode.h
Normal 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
|
||||
122
examples/SynchronousKittens/Sample/BlurbNode.m
Normal 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
|
||||
36
examples/SynchronousKittens/Sample/Info.plist
Normal 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>
|
||||
24
examples/SynchronousKittens/Sample/KittenNode.h
Normal 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
|
||||
197
examples/SynchronousKittens/Sample/KittenNode.mm
Normal 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
|
||||
16
examples/SynchronousKittens/Sample/ViewController.h
Normal 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
|
||||
216
examples/SynchronousKittens/Sample/ViewController.m
Normal 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
|
||||
20
examples/SynchronousKittens/Sample/main.m
Normal 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]));
|
||||
}
|
||||
}
|
||||