Merge branch 'master' into compiler-warnings

This commit is contained in:
Paul Young
2015-09-16 12:13:50 -07:00
79 changed files with 2616 additions and 626 deletions

View File

@@ -139,6 +139,10 @@
05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */; };
05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; };
18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */; };
18C2ED831B9B7DE800F627B3 /* ASCollectionNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */; };
1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; };
204C979E1B362CB3002B1083 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 204C979D1B362CB3002B1083 /* Default-568h@2x.png */; };
205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -218,18 +222,24 @@
509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; };
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, ); }; };
9C204A641B86349B00313849 /* ASBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C204A621B86349B00313849 /* ASBaselinePositionedLayout.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C204A651B86349B00313849 /* ASBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C204A621B86349B00313849 /* ASBaselinePositionedLayout.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C204A661B86349B00313849 /* ASBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C204A631B86349B00313849 /* ASBaselinePositionedLayout.mm */; };
9C204A671B86349B00313849 /* ASBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C204A631B86349B00313849 /* ASBaselinePositionedLayout.mm */; };
9C204A6A1B87803A00313849 /* ASBaselineLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C204A681B87803A00313849 /* ASBaselineLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C204A6B1B87803A00313849 /* ASBaselineLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C204A681B87803A00313849 /* ASBaselineLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C3061061B857EC400D0530B /* ASBaselineLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C3061041B857EC400D0530B /* ASBaselineLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C3061071B857EC400D0530B /* ASBaselineLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C3061041B857EC400D0530B /* ASBaselineLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C3061081B857EC400D0530B /* ASBaselineLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C3061051B857EC400D0530B /* ASBaselineLayoutSpec.mm */; };
9C3061091B857EC400D0530B /* ASBaselineLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C3061051B857EC400D0530B /* ASBaselineLayoutSpec.mm */; };
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, ); }; };
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 */; };
9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C5FA3501B8F6ADF00A62714 /* ASLayoutOptions.mm */; };
9C5FA35F1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C5FA35C1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm */; };
9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C5FA35C1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm */; };
9C65A72A1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; };
9C65A72B1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; };
9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C6BB3B31B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; settings = {ASSET_TAGS = (); }; };
9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; settings = {ASSET_TAGS = (); }; };
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; settings = {ASSET_TAGS = (); }; };
9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; settings = {ASSET_TAGS = (); }; };
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 */; };
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, ); }; };
@@ -464,7 +474,7 @@
058D09DD195D050800B7D73C /* ASImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageNode.h; sourceTree = "<group>"; };
058D09DE195D050800B7D73C /* ASImageNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASImageNode.mm; sourceTree = "<group>"; };
058D09DF195D050800B7D73C /* ASTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNode.h; sourceTree = "<group>"; };
058D09E0195D050800B7D73C /* ASTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTextNode.mm; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
058D09E0195D050800B7D73C /* ASTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTextNode.mm; sourceTree = "<group>"; };
058D09E2195D050800B7D73C /* _ASDisplayLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayLayer.h; sourceTree = "<group>"; };
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayLayer.mm; sourceTree = "<group>"; };
058D09E4195D050800B7D73C /* _ASDisplayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayView.h; sourceTree = "<group>"; };
@@ -529,6 +539,8 @@
05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASDealloc2MainObject.m; path = ../Details/ASDealloc2MainObject.m; sourceTree = "<group>"; };
05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASSnapshotTestCase.mm; sourceTree = "<group>"; };
05F20AA31A15733C00DCA68A /* ASImageProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageProtocols.h; sourceTree = "<group>"; };
18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionNode.h; sourceTree = "<group>"; };
18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionNode.m; sourceTree = "<group>"; };
1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHelpers.h; sourceTree = "<group>"; };
204C979D1B362CB3002B1083 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = "<group>"; };
205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionViewLayout+ASConvenience.h"; sourceTree = "<group>"; };
@@ -567,12 +579,15 @@
4640521E1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMultidimensionalArrayUtils.h; sourceTree = "<group>"; };
4640521F1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMultidimensionalArrayUtils.mm; sourceTree = "<group>"; };
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = "<group>"; };
9C204A621B86349B00313849 /* ASBaselinePositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBaselinePositionedLayout.h; sourceTree = "<group>"; };
9C204A631B86349B00313849 /* ASBaselinePositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBaselinePositionedLayout.mm; sourceTree = "<group>"; };
9C204A681B87803A00313849 /* ASBaselineLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASBaselineLayoutable.h; path = AsyncDisplayKit/Layout/ASBaselineLayoutable.h; sourceTree = "<group>"; };
9C3061041B857EC400D0530B /* ASBaselineLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASBaselineLayoutSpec.h; path = AsyncDisplayKit/Layout/ASBaselineLayoutSpec.h; sourceTree = "<group>"; };
9C3061051B857EC400D0530B /* ASBaselineLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASBaselineLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASBaselineLayoutSpec.mm; sourceTree = "<group>"; };
9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; 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>"; };
9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutOptionsPrivate.h; sourceTree = "<group>"; };
9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStaticLayoutable.h; path = AsyncDisplayKit/Layout/ASStaticLayoutable.h; sourceTree = "<group>"; };
9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackBaselinePositionedLayout.h; sourceTree = "<group>"; };
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>"; };
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>"; };
@@ -736,6 +751,8 @@
children = (
055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */,
AC6456071B0A335000CF11B8 /* ASCellNode.m */,
18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */,
18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */,
AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */,
AC3C4A501A1139C100143C57 /* ASCollectionView.mm */,
AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */,
@@ -911,6 +928,9 @@
058D0A01195D050800B7D73C /* Private */ = {
isa = PBXGroup;
children = (
9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */,
9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */,
9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */,
296A0A2C1A9516B2005ACEAA /* ASBatchFetching.h */,
2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */,
296A0A2D1A9516B2005ACEAA /* ASBatchFetching.m */,
@@ -937,8 +957,6 @@
ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */,
ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */,
ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */,
9C204A621B86349B00313849 /* ASBaselinePositionedLayout.h */,
9C204A631B86349B00313849 /* ASBaselinePositionedLayout.mm */,
);
path = Private;
sourceTree = "<group>";
@@ -961,7 +979,6 @@
children = (
ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */,
ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */,
9C204A681B87803A00313849 /* ASBaselineLayoutable.h */,
ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */,
ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */,
ACF6ED071B17843500DA7C62 /* ASDimension.h */,
@@ -973,6 +990,7 @@
ACF6ED0B1B17843500DA7C62 /* ASLayout.h */,
ACF6ED0C1B17843500DA7C62 /* ASLayout.mm */,
ACF6ED111B17843500DA7C62 /* ASLayoutable.h */,
9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */,
ACF6ED0D1B17843500DA7C62 /* ASLayoutSpec.h */,
ACF6ED0E1B17843500DA7C62 /* ASLayoutSpec.mm */,
ACF6ED121B17843500DA7C62 /* ASOverlayLayoutSpec.h */,
@@ -983,10 +1001,12 @@
AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */,
ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */,
ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */,
9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */,
ACF6ED181B17843500DA7C62 /* ASStaticLayoutSpec.h */,
ACF6ED191B17843500DA7C62 /* ASStaticLayoutSpec.mm */,
9C3061041B857EC400D0530B /* ASBaselineLayoutSpec.h */,
9C3061051B857EC400D0530B /* ASBaselineLayoutSpec.mm */,
9C5FA34F1B8F6ADF00A62714 /* ASLayoutOptions.h */,
9C5FA3501B8F6ADF00A62714 /* ASLayoutOptions.mm */,
9C5FA35C1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm */,
);
name = Layout;
path = ..;
@@ -1035,23 +1055,26 @@
ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */,
ACF6ED2B1B17843500DA7C62 /* ASOverlayLayoutSpec.h in Headers */,
ACF6ED1C1B17843500DA7C62 /* ASCenterLayoutSpec.h in Headers */,
9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */,
ACF6ED2A1B17843500DA7C62 /* ASLayoutable.h in Headers */,
ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */,
ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */,
ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */,
9C65A72A1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */,
ACF6ED1A1B17843500DA7C62 /* ASBackgroundLayoutSpec.h in Headers */,
291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */,
464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */,
464052241A3F83C40061C0BA /* ASLayoutController.h in Headers */,
464052201A3F83C40061C0BA /* ASDataController.h in Headers */,
05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */,
9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */,
0516FA401A1563D200B4EBED /* ASMultiplexImageNode.h in Headers */,
9C204A6A1B87803A00313849 /* ASBaselineLayoutable.h in Headers */,
058D0A47195D05CB00B7D73C /* ASControlNode.h in Headers */,
058D0A48195D05CB00B7D73C /* ASControlNode.m in Headers */,
058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */,
058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */,
1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */,
9C5FA3511B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */,
058D0A4B195D05CB00B7D73C /* ASDisplayNode.mm in Headers */,
430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */,
058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */,
@@ -1068,7 +1091,6 @@
0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */,
055F1A3C19ABD43F004DAFF1 /* ASCellNode.h in Headers */,
058D0A53195D05DC00B7D73C /* _ASDisplayLayer.h in Headers */,
9C204A641B86349B00313849 /* ASBaselinePositionedLayout.h in Headers */,
058D0A54195D05DC00B7D73C /* _ASDisplayLayer.mm in Headers */,
058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */,
058D0A56195D05DC00B7D73C /* _ASDisplayView.mm in Headers */,
@@ -1095,12 +1117,13 @@
058D0A67195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Headers */,
058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */,
205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */,
9C3061061B857EC400D0530B /* ASBaselineLayoutSpec.h in Headers */,
058D0A69195D05EC00B7D73C /* _ASAsyncTransaction.m in Headers */,
058D0A6A195D05EC00B7D73C /* _ASAsyncTransactionContainer+Private.h in Headers */,
058D0A6B195D05EC00B7D73C /* _ASAsyncTransactionContainer.h in Headers */,
18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
058D0A6C195D05EC00B7D73C /* _ASAsyncTransactionContainer.m in Headers */,
6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */,
9CDC18CC1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */,
058D0A6D195D05EC00B7D73C /* _ASAsyncTransactionGroup.h in Headers */,
058D0A6E195D05EC00B7D73C /* _ASAsyncTransactionGroup.m in Headers */,
205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */,
@@ -1149,6 +1172,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
9C6BB3B31B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */,
B35062321B010EFD0018CF92 /* ASTextNodeShadower.h in Headers */,
34EFC7651B701CCC00AD841F /* ASRelativeSize.h in Headers */,
B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */,
@@ -1160,17 +1184,16 @@
B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */,
B35062371B010EFD0018CF92 /* ASTextNodeWordKerner.h in Headers */,
B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */,
9C204A651B86349B00313849 /* ASBaselinePositionedLayout.h in Headers */,
B35062111B010EFD0018CF92 /* _ASDisplayView.h in Headers */,
B35061F81B010EFD0018CF92 /* ASControlNode.h in Headers */,
430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */,
B35062281B010EFD0018CF92 /* ASRangeHandler.h in Headers */,
B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */,
18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */,
B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */,
34EFC76C1B701CED00AD841F /* ASOverlayLayoutSpec.h in Headers */,
B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */,
9C3061071B857EC400D0530B /* ASBaselineLayoutSpec.h in Headers */,
B35062571B010F070018CF92 /* ASAssert.h in Headers */,
B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */,
B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */,
@@ -1181,6 +1204,7 @@
B35062391B010EFD0018CF92 /* ASThread.h in Headers */,
B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */,
34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */,
9C5FA3521B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */,
34EFC76A1B701CE600AD841F /* ASLayoutSpec.h in Headers */,
B35062221B010EFD0018CF92 /* ASMultidimensionalArrayUtils.h in Headers */,
B350625B1B010F070018CF92 /* ASEqualityHelpers.h in Headers */,
@@ -1214,7 +1238,6 @@
34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */,
B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */,
34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */,
9C204A6B1B87803A00313849 /* ASBaselineLayoutable.h in Headers */,
509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */,
B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */,
B35062211B010EFD0018CF92 /* ASLayoutRangeType.h in Headers */,
@@ -1226,6 +1249,7 @@
34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */,
B35062301B010EFD0018CF92 /* ASTextNodeRenderer.h in Headers */,
509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */,
9C65A72B1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */,
B350620D1B010EFD0018CF92 /* ASTextNode.h in Headers */,
34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */,
B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */,
@@ -1234,9 +1258,11 @@
B35062001B010EFD0018CF92 /* ASEditableTextNode.h in Headers */,
B350623A1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.h in Headers */,
B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */,
9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */,
B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */,
B35062191B010EFD0018CF92 /* ASDealloc2MainObject.h in Headers */,
B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */,
9CDC18CD1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */,
B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1437,10 +1463,11 @@
058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */,
058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */,
058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */,
9C204A661B86349B00313849 /* ASBaselinePositionedLayout.mm in Sources */,
ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */,
058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */,
ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */,
9C5FA3531B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */,
9C5FA35F1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */,
ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */,
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */,
@@ -1455,6 +1482,7 @@
205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */,
AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */,
ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */,
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
ACF6ED211B17843500DA7C62 /* ASDimension.mm in Sources */,
464052261A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm in Sources */,
055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */,
@@ -1470,13 +1498,13 @@
464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */,
058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */,
0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */,
18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */,
058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */,
058D0A25195D050800B7D73C /* UIView+ASConvenience.m in Sources */,
0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */,
058D0A14195D050800B7D73C /* ASDisplayNode.mm in Sources */,
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */,
9C3061081B857EC400D0530B /* ASBaselineLayoutSpec.mm in Sources */,
058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */,
058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */,
058D0A1C195D050800B7D73C /* ASTextNodeCoreTextAdditions.m in Sources */,
@@ -1538,10 +1566,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9C3061091B857EC400D0530B /* ASBaselineLayoutSpec.mm in Sources */,
34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */,
B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
9C204A671B86349B00313849 /* ASBaselinePositionedLayout.mm in Sources */,
B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */,
B35062311B010EFD0018CF92 /* ASTextNodeRenderer.mm in Sources */,
B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */,
@@ -1552,6 +1578,7 @@
B35062471B010EFD0018CF92 /* ASBatchFetching.m in Sources */,
B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */,
34EFC76F1B701CF700AD841F /* ASRatioLayoutSpec.mm in Sources */,
9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */,
34EFC7681B701CDE00AD841F /* ASLayout.mm in Sources */,
B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */,
34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */,
@@ -1567,8 +1594,10 @@
B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */,
B35062231B010EFD0018CF92 /* ASMultidimensionalArrayUtils.mm in Sources */,
509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */,
9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */,
B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */,
B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */,
18C2ED831B9B7DE800F627B3 /* ASCollectionNode.m in Sources */,
B35061F91B010EFD0018CF92 /* ASControlNode.m in Sources */,
AC47D9421B3B891B00AAEE9D /* ASCellNode.m in Sources */,
509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */,
@@ -1583,6 +1612,7 @@
B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */,
B35062091B010EFD0018CF92 /* ASScrollNode.m in Sources */,
B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */,
9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */,
34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */,
B35062381B010EFD0018CF92 /* ASTextNodeWordKerner.m in Sources */,

View File

@@ -31,13 +31,13 @@
return self;
}
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
{
ASDisplayNodeAssertNotSupported();
return nil;
}
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
{
ASDisplayNodeAssertNotSupported();
return nil;

View File

@@ -0,0 +1,21 @@
//
// ASCollectionNode.h
// AsyncDisplayKit
//
// Created by Scott Goodson on 9/5/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/AsyncDisplayKit.h>
/**
* ASCollectionNode is a node based class that wraps an ASCollectionView. It can be used
* as a subnode of another node, and provide room for many (great) features and improvements later on.
*/
@interface ASCollectionNode : ASDisplayNode
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) ASCollectionView *view;
@end

View File

@@ -0,0 +1,33 @@
//
// ASCollectionNode.m
// AsyncDisplayKit
//
// Created by Scott Goodson on 9/5/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import "ASCollectionNode.h"
@implementation ASCollectionNode
- (instancetype)init
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
self = [self initWithCollectionViewLayout:nil]; // Will throw an exception for lacking a UICV Layout.
return nil;
}
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout
{
if (self = [super initWithViewBlock:^UIView *{ return [[ASCollectionView alloc] initWithCollectionViewLayout:layout]; }]) {
return self;
}
return nil;
}
- (ASCollectionView *)view
{
return (ASCollectionView *)[super view];
}
@end

View File

@@ -27,6 +27,8 @@
*/
@interface ASCollectionView : UICollectionView
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout;
@property (nonatomic, weak) id<ASCollectionViewDataSource> asyncDataSource;
@property (nonatomic, weak) id<ASCollectionViewDelegate> asyncDelegate; // must not be nil

View File

@@ -126,6 +126,7 @@ static BOOL _isInterceptedSelector(SEL sel)
ASBatchContext *_batchContext;
CGSize _maxSizeForNodesConstrainedSize;
BOOL _ignoreMaxSizeChange;
}
@property (atomic, assign) BOOL asyncDataSourceLocked;
@@ -137,6 +138,11 @@ static BOOL _isInterceptedSelector(SEL sel)
#pragma mark -
#pragma mark Lifecycle.
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout
{
return [self initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
}
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
{
return [self initWithFrame:frame collectionViewLayout:layout asyncDataFetching:NO];
@@ -174,6 +180,11 @@ static BOOL _isInterceptedSelector(SEL sel)
_collectionViewLayoutImplementsInsetSection = [layout respondsToSelector:@selector(sectionInset)];
_maxSizeForNodesConstrainedSize = self.bounds.size;
// 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.
_ignoreMaxSizeChange = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero);
self.backgroundColor = [UIColor whiteColor];
[self registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"_ASCollectionViewCell"];
@@ -474,14 +485,22 @@ static BOOL _isInterceptedSelector(SEL sel)
- (void)layoutSubviews
{
[super layoutSubviews];
if (! CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, self.bounds.size)) {
_maxSizeForNodesConstrainedSize = self.bounds.size;
[self performBatchAnimated:NO updates:^{
[_dataController relayoutAllRows];
} completion:nil];
// First size 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 (_ignoreMaxSizeChange) {
_ignoreMaxSizeChange = NO;
} else {
[self performBatchAnimated:NO updates:^{
[_dataController relayoutAllRows];
} completion:nil];
}
}
// To ensure _maxSizeForNodesConstrainedSize is up-to-date for every usage, this call to super must be done last
[super layoutSubviews];
}

View File

@@ -13,17 +13,25 @@
#import <AsyncDisplayKit/ASDealloc2MainObject.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASStackLayoutable.h>
#import <AsyncDisplayKit/ASLayoutable.h>
@class ASDisplayNode;
/**
* UIView creation block. Used to create the backing view of a new display node.
*/
typedef UIView *(^ASDisplayNodeViewBlock)();
/**
* CALayer creation block. Used to create the backing layer of a new display node.
*/
typedef CALayer *(^ASDisplayNodeLayerBlock)();
/**
* ASDisplayNode loaded callback block. This block is called BEFORE the -didLoad method and is always called on the main thread.
*/
typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
/**
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view
* hierarchy off the main thread, and could do rendering off the main thread as well.
@@ -40,7 +48,7 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)();
*
*/
@interface ASDisplayNode : ASDealloc2MainObject <ASStackLayoutable>
@interface ASDisplayNode : ASDealloc2MainObject <ASLayoutable>
/** @name Initializing a node object */
@@ -65,15 +73,37 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)();
*/
- (id)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock;
/**
* @abstract Alternative initializer with a block to create the backing view.
*
* @param viewBlock The block that will be used to create the backing view.
* @param didLoadBlock The block that will be called after the view created by the viewBlock is loaded
*
* @return An ASDisplayNode instance that loads its view with the given block that is guaranteed to run on the main
* queue. The view will render synchronously and -layout and touch handling methods on the node will not be called.
*/
- (id)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock;
/**
* @abstract Alternative initializer with a block to create the backing layer.
*
* @param viewBlock The block that will be used to create the backing layer.
* @param layerBlock The block that will be used to create the backing layer.
*
* @return An ASDisplayNode instance that loads its layer with the given block that is guaranteed to run on the main
* queue. The layer will render synchronously and -layout and touch handling methods on the node will not be called.
*/
- (id)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock;
- (id)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock;
/**
* @abstract Alternative initializer with a block to create the backing layer.
*
* @param layerBlock The block that will be used to create the backing layer.
* @param didLoadBlock The block that will be called after the layer created by the layerBlock is loaded
*
* @return An ASDisplayNode instance that loads its layer with the given block that is guaranteed to run on the main
* queue. The layer will render synchronously and -layout and touch handling methods on the node will not be called.
*/
- (id)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock;
/** @name Properties */

View File

@@ -9,6 +9,7 @@
#import "ASDisplayNode.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNodeInternal.h"
#import "ASLayoutOptionsPrivate.h"
#import <objc/runtime.h>
@@ -30,6 +31,8 @@
*
*/
- (void)_staticInitialize;
@end
// Conditionally time these scopes to our debug ivars (only exist in debug/profile builds)
@@ -41,13 +44,10 @@
@implementation ASDisplayNode
@synthesize spacingBefore = _spacingBefore;
@synthesize spacingAfter = _spacingAfter;
@synthesize flexGrow = _flexGrow;
@synthesize flexShrink = _flexShrink;
@synthesize flexBasis = _flexBasis;
@synthesize alignSelf = _alignSelf;
// these dynamic properties all defined in ASLayoutOptionsPrivate.m
@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition, layoutOptions;
@synthesize preferredFrameSize = _preferredFrameSize;
@synthesize isFinalLayoutable = _isFinalLayoutable;
BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector)
{
@@ -85,25 +85,104 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)
}
}
+ (void)initialize
/**
* Returns ASDisplayNodeFlags for the givern class/instance. instance MAY BE NIL.
*
* @param c the class, required
* @param instance the instance, which may be nil. (If so, the class is inspected instead)
* @remarks The instance value is used only if we suspect the class may be dynamic (because it overloads
* +respondsToSelector: or -respondsToSelector.) In that case we use our "slow path", calling this
* method on each -init and passing the instance value. While this may seem like an unlikely scenario,
* it turns our our own internal tests use a dynamic class, so it's worth capturing this edge case.
*
* @return ASDisplayNode flags.
*/
static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *instance)
{
if (self == [ASDisplayNode class]) {
return;
ASDisplayNodeCAssertNotNil(c, @"class is required");
struct ASDisplayNodeFlags flags = {0};
flags.isInHierarchy = NO;
flags.displaysAsynchronously = YES;
flags.implementsDrawRect = ([c respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0);
flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
if (instance) {
flags.implementsDrawParameters = ([instance respondsToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0);
} else {
flags.implementsDrawParameters = ([c instancesRespondToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0);
}
return flags;
}
/**
* Returns ASDisplayNodeMethodOverrides for the given class
*
* @param c the class, requireed.
*
* @return ASDisplayNodeMethodOverrides.
*/
static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
{
ASDisplayNodeCAssertNotNil(c, @"class is required");
ASDisplayNodeMethodOverrides overrides = ASDisplayNodeMethodOverrideNone;
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesBegan:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesBegan;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesMoved:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesMoved;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesCancelled:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesCancelled;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesEnded:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesEnded;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(layoutSpecThatFits:))) {
overrides |= ASDisplayNodeMethodOverrideLayoutSpecThatFits;
}
// Subclasses should never override these
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method", NSStringFromClass(self));
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedLayout)), @"Subclass %@ must not override calculatedLayout method", NSStringFromClass(self));
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measure:)), @"Subclass %@ must not override measure method", NSStringFromClass(self));
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measureWithSizeRange:)), @"Subclass %@ must not override measureWithSizeRange method", NSStringFromClass(self));
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method", NSStringFromClass(self));
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearFetchedData)), @"Subclass %@ must not override recursivelyClearFetchedData method", NSStringFromClass(self));
// At most one of the three layout methods is overridden
ASDisplayNodeAssert((ASDisplayNodeSubclassOverridesSelector(self, @selector(calculateSizeThatFits:)) ? 1 : 0)
+ (ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutSpecThatFits:)) ? 1 : 0)
+ (ASDisplayNodeSubclassOverridesSelector(self, @selector(calculateLayoutThatFits:)) ? 1 : 0) <= 1,
@"Subclass %@ must override at most one of the three layout methods: calculateLayoutThatFits, layoutSpecThatFits or calculateSizeThatFits", NSStringFromClass(self));
return overrides;
}
+ (void)initialize
{
if (self != [ASDisplayNode class]) {
// Subclasses should never override these
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method", NSStringFromClass(self));
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedLayout)), @"Subclass %@ must not override calculatedLayout method", NSStringFromClass(self));
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measure:)), @"Subclass %@ must not override measure method", NSStringFromClass(self));
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measureWithSizeRange:)), @"Subclass %@ must not override measureWithSizeRange method", NSStringFromClass(self));
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method", NSStringFromClass(self));
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearFetchedData)), @"Subclass %@ must not override recursivelyClearFetchedData method", NSStringFromClass(self));
// At most one of the three layout methods is overridden
ASDisplayNodeAssert((ASDisplayNodeSubclassOverridesSelector(self, @selector(calculateSizeThatFits:)) ? 1 : 0)
+ (ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutSpecThatFits:)) ? 1 : 0)
+ (ASDisplayNodeSubclassOverridesSelector(self, @selector(calculateLayoutThatFits:)) ? 1 : 0) <= 1,
@"Subclass %@ must override at most one of the three layout methods: calculateLayoutThatFits, layoutSpecThatFits or calculateSizeThatFits", NSStringFromClass(self));
}
// Below we are pre-calculating values per-class and dynamically adding a method (_staticInitialize) to populate these values
// when each instance is constructed. These values don't change for each class, so there is significant performance benefit
// in doing it here. +initialize is guaranteed to be called before any instance method so it is safe to add this method here.
// Note that we take care to detect if the class overrides +respondsToSelector: or -respondsToSelector and take the slow path
// (recalculating for each instance) to make sure we are always correct.
BOOL classOverridesRespondsToSelector = ASSubclassOverridesClassSelector([NSObject class], self, @selector(respondsToSelector:));
BOOL instancesOverrideRespondsToSelector = ASSubclassOverridesSelector([NSObject class], self, @selector(respondsToSelector:));
struct ASDisplayNodeFlags flags = GetASDisplayNodeFlags(self, nil);
ASDisplayNodeMethodOverrides methodOverrides = GetASDisplayNodeMethodOverrides(self);
IMP staticInitialize = imp_implementationWithBlock(^(ASDisplayNode *node) {
node->_flags = (classOverridesRespondsToSelector || instancesOverrideRespondsToSelector) ? GetASDisplayNodeFlags(node.class, node) : flags;
node->_methodOverrides = (classOverridesRespondsToSelector) ? GetASDisplayNodeMethodOverrides(node.class) : methodOverrides;
});
class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@");
}
+ (BOOL)layerBackedNodesEnabled
@@ -123,39 +202,16 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)
#pragma mark - Lifecycle
- (void)_staticInitialize
{
ASDisplayNodeAssert(NO, @"_staticInitialize must be overridden");
}
- (void)_initializeInstance
{
[self _staticInitialize];
_contentsScaleForDisplay = ASScreenScale();
_displaySentinel = [[ASSentinel alloc] init];
_flags.isInHierarchy = NO;
_flags.displaysAsynchronously = YES;
// As an optimization, it may be worth a caching system that performs these checks once per class in +initialize (see above).
_flags.implementsDrawRect = ([[self class] respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0);
_flags.implementsImageDisplay = ([[self class] respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
_flags.implementsDrawParameters = ([self respondsToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0);
ASDisplayNodeMethodOverrides overrides = ASDisplayNodeMethodOverrideNone;
if (ASDisplayNodeSubclassOverridesSelector([self class], @selector(touchesBegan:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesBegan;
}
if (ASDisplayNodeSubclassOverridesSelector([self class], @selector(touchesMoved:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesMoved;
}
if (ASDisplayNodeSubclassOverridesSelector([self class], @selector(touchesCancelled:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesCancelled;
}
if (ASDisplayNodeSubclassOverridesSelector([self class], @selector(touchesEnded:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesEnded;
}
if (ASDisplayNodeSubclassOverridesSelector([self class], @selector(layoutSpecThatFits:))) {
overrides |= ASDisplayNodeMethodOverrideLayoutSpecThatFits;
}
_methodOverrides = overrides;
_flexBasis = ASRelativeDimensionUnconstrained;
_preferredFrameSize = CGSizeZero;
}
@@ -200,33 +256,47 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)
- (id)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock
{
if (!(self = [super init]))
return nil;
ASDisplayNodeAssertNotNil(viewBlock, @"should initialize with a valid block that returns a UIView");
[self _initializeInstance];
_viewBlock = viewBlock;
_flags.synchronous = YES;
return self;
return [self initWithViewBlock:viewBlock didLoadBlock:nil];
}
- (id)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock
- (id)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
{
if (!(self = [super init]))
return nil;
ASDisplayNodeAssertNotNil(layerBlock, @"should initialize with a valid block that returns a CALayer");
ASDisplayNodeAssertNotNil(viewBlock, @"should initialize with a valid block that returns a UIView");
[self _initializeInstance];
_layerBlock = layerBlock;
_viewBlock = viewBlock;
_nodeLoadedBlock = didLoadBlock;
_flags.synchronous = YES;
_flags.layerBacked = YES;
return self;
}
- (id)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock
{
return [self initWithLayerBlock:layerBlock didLoadBlock:nil];
}
- (id)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
{
if (!(self = [super init]))
return nil;
ASDisplayNodeAssertNotNil(layerBlock, @"should initialize with a valid block that returns a CALayer");
[self _initializeInstance];
_layerBlock = layerBlock;
_nodeLoadedBlock = didLoadBlock;
_flags.synchronous = YES;
_flags.layerBacked = YES;
return self;
}
- (void)dealloc
{
ASDisplayNodeAssertMainThread();
@@ -368,7 +438,7 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)
}
{
TIME_SCOPED(_debugTimeForDidLoad);
[self didLoad];
[self __didLoad];
}
if (self.placeholderEnabled) {
@@ -600,9 +670,27 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)
} else {
// This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used.
[self measureWithSizeRange:oldConstrainedSize];
CGRect bounds = self.bounds;
bounds.size = CGSizeMake(_layout.size.width, _layout.size.height);
self.bounds = bounds;
CGSize oldSize = self.bounds.size;
CGSize newSize = _layout.size;
if (! CGSizeEqualToSize(oldSize, newSize)) {
CGRect bounds = self.bounds;
bounds.size = newSize;
self.bounds = bounds;
// Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint
// and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted.
BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain());
CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint);
CGPoint oldPosition = (useLayer ? _layer.position : self.position);
CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x;
CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y;
CGPoint newPosition = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta);
useLayer ? _layer.position = newPosition : self.position = newPosition;
}
}
}
@@ -612,10 +700,10 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)
{
ASDisplayNodeAssertMainThread();
ASDN::MutexLocker l(_propertyLock);
if (CGRectEqualToRect(_layer.bounds, CGRectZero)) {
if (CGRectEqualToRect(self.bounds, CGRectZero)) {
return; // Performing layout on a zero-bounds view often results in frame calculations with negative sizes after applying margins, which will cause measureWithSizeRange: on subnodes to assert.
}
_placeholderLayer.frame = _layer.bounds;
_placeholderLayer.frame = self.bounds;
[self layout];
[self layoutDidFinish];
}
@@ -1408,6 +1496,15 @@ static NSInteger incrementIfFound(NSInteger i) {
_flags.isMeasured = NO;
}
- (void)__didLoad
{
if (_nodeLoadedBlock) {
_nodeLoadedBlock(self);
_nodeLoadedBlock = nil;
}
[self didLoad];
}
- (void)didLoad
{
ASDisplayNodeAssertMainThread();
@@ -1836,6 +1933,11 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
return !self.layerBacked && [self.view canPerformAction:action withSender:sender];
}
- (id<ASLayoutable>)finalLayoutable
{
return self;
}
@end
@implementation ASDisplayNode (Debugging)

View File

@@ -86,13 +86,13 @@
return self;
}
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
{
ASDisplayNodeAssertNotSupported();
return nil;
}
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
{
ASDisplayNodeAssertNotSupported();
return nil;

View File

@@ -96,13 +96,13 @@
return self;
}
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
{
ASDisplayNodeAssertNotSupported();
return nil;
}
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
{
ASDisplayNodeAssertNotSupported();
return nil;

View File

@@ -99,16 +99,11 @@
- (void)beginUpdates;
/**
* Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view.
* Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view, with animation enabled and no completion block.
* You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations
* to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating
* the operations simultaneously. This method is must be called from the main thread. It's important to remeber that the ASTableView will
* be processing the updates asynchronously after this call is completed.
*
* @param animated NO to disable all animations.
* @param completion A completion handler block to execute when all of the operations are finished. This block takes a single
* Boolean parameter that contains the value YES if all of the related animations completed successfully or
* NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread.
*/
- (void)endUpdates;
@@ -271,17 +266,6 @@
@optional
/**
* Provides the constrained size range for measuring the node at the index path.
*
* @param tableView The sender.
*
* @param indexPath The index path of the node.
*
* @returns A constrained size range for layout the node at this index path.
*/
- (ASSizeRange)tableView:(ASTableView *)tableView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
/**
* Indicator to lock the data source for data fetching in async mode.
* We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception

View File

@@ -16,6 +16,7 @@
#import "ASDisplayNodeInternal.h"
#import "ASBatchFetching.h"
#import "ASInternalHelpers.h"
#import "ASLayout.h"
//#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...)
@@ -104,22 +105,31 @@ static BOOL _isInterceptedSelector(SEL sel)
#pragma mark -
#pragma mark ASCellNode<->UITableViewCell bridging.
@class _ASTableViewCell;
@protocol _ASTableViewCellDelegate <NSObject>
- (void)tableViewCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath didTransitionToState:(UITableViewCellStateMask)state;
- (void)willLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell;
@end
@interface _ASTableViewCell : UITableViewCell
@property (nonatomic, weak) id<_ASTableViewCellDelegate> delegate;
@property (nonatomic) NSIndexPath *indexPath;
@property (nonatomic, weak) ASCellNode *node;
@end
@implementation _ASTableViewCell
// TODO add assertions to prevent use of view-backed UITableViewCell properties (eg .textLabel)
- (void)layoutSubviews
{
[_delegate willLayoutSubviewsOfTableViewCell:self];
[super layoutSubviews];
}
- (void)didTransitionToState:(UITableViewCellStateMask)state
{
[self setNeedsLayout];
[self layoutIfNeeded];
[super didTransitionToState:state];
[_delegate tableViewCell:self atIndexPath:_indexPath didTransitionToState:state];
}
@end
@@ -146,9 +156,8 @@ static BOOL _isInterceptedSelector(SEL sel)
NSIndexPath *_contentOffsetAdjustmentTopVisibleRow;
CGFloat _contentOffsetAdjustment;
BOOL _asyncDataSourceImplementsConstrainedSizeForNode;
CGFloat _maxWidthForNodesConstrainedSize;
BOOL _ignoreMaxWidthChange;
}
@property (atomic, assign) BOOL asyncDataSourceLocked;
@@ -203,6 +212,9 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
_automaticallyAdjustsContentOffset = NO;
_maxWidthForNodesConstrainedSize = 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);
}
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
@@ -267,12 +279,10 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
super.dataSource = nil;
_asyncDataSource = nil;
_proxyDataSource = nil;
_asyncDataSourceImplementsConstrainedSizeForNode = NO;
} else {
_asyncDataSource = asyncDataSource;
_proxyDataSource = [[_ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
super.dataSource = (id<UITableViewDataSource>)_proxyDataSource;
_asyncDataSourceImplementsConstrainedSizeForNode = ([_asyncDataSource respondsToSelector:@selector(tableView:constrainedSizeForNodeAtIndexPath:)] ? 1 : 0);
}
}
@@ -367,14 +377,22 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
- (void)layoutSubviews
{
[super layoutSubviews];
if (_maxWidthForNodesConstrainedSize != self.bounds.size.width) {
_maxWidthForNodesConstrainedSize = self.bounds.size.width;
[self beginUpdates];
[_dataController relayoutAllRows];
[self endUpdates];
// 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;
} else {
[self beginUpdates];
[_dataController relayoutAllRows];
[self endUpdates];
}
}
// To ensure _maxWidthForNodesConstrainedSize is up-to-date for every usage, this call to super must be done last
[super layoutSubviews];
}
#pragma mark -
@@ -495,7 +513,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
[_rangeController configureContentView:cell.contentView forCellNode:node];
cell.indexPath = indexPath;
cell.node = node;
cell.backgroundColor = node.backgroundColor;
cell.selectionStyle = node.selectionStyle;
@@ -786,12 +804,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{
if (_asyncDataSourceImplementsConstrainedSizeForNode) {
return [_asyncDataSource tableView:self constrainedSizeForNodeAtIndexPath:indexPath];
}
// Default size range
return ASSizeRangeMake(CGSizeZero, CGSizeMake(_maxWidthForNodesConstrainedSize, FLT_MAX));
return ASSizeRangeMake(CGSizeMake(_maxWidthForNodesConstrainedSize, 0),
CGSizeMake(_maxWidthForNodesConstrainedSize, FLT_MAX));
}
- (void)dataControllerLockDataSource
@@ -832,22 +846,31 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
#pragma mark - _ASTableViewCellDelegate
- (void)tableViewCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath didTransitionToState:(UITableViewCellStateMask)state
- (void)willLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell
{
[self beginUpdates];
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width;
ASCellNode *node = tableViewCell.node;
ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout;
ASSizeRange constrainedSize = [self dataController:_dataController constrainedSizeForNodeAtIndexPath:indexPath];
if (state != UITableViewCellStateDefaultMask) {
// Edit control or delete confirmation was shown and size of content view was changed.
// The new size should be taken into consideration.
constrainedSize.min.width = MIN(cell.contentView.frame.size.width, constrainedSize.min.width);
constrainedSize.max.width = MIN(cell.contentView.frame.size.width, constrainedSize.max.width);
// Table view cells should always fill its content view width.
// Normally the content view width equals to the constrained size width (which equals to the table view width).
// If there is a mismatch between these values, for example after the table view entered or left editing mode,
// content view width is preferred and used to re-measure the cell node.
if (contentViewWidth != constrainedSize.max.width) {
constrainedSize.min.width = contentViewWidth;
constrainedSize.max.width = contentViewWidth;
// Re-measurement is done on main to ensure thread affinity. In the worst case, this is as fast as UIKit's implementation.
//
// Unloaded nodes *could* be re-measured off the main thread, but only with the assumption that content view width
// is the same for all cells (because there is no easy way to get that individual value before the node being assigned to a _ASTableViewCell).
// Also, in many cases, some nodes may not need to be re-measured at all, such as when user enters and then immediately leaves editing mode.
// To avoid premature optimization and making such assumption, as well as to keep ASTableView simple, re-measurement is strictly done on main.
[self beginUpdates];
CGSize calculatedSize = [[node measureWithSizeRange:constrainedSize] size];
node.frame = CGRectMake(0, 0, calculatedSize.width, calculatedSize.height);
[self endUpdates];
}
[node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0, 0, node.calculatedSize.width, node.calculatedSize.height);
[self endUpdates];
}
@end

View File

@@ -7,7 +7,6 @@
*/
#import <AsyncDisplayKit/ASControlNode.h>
#import <AsyncDisplayKit/ASBaselineLayoutable.h>
@protocol ASTextNodeDelegate;
@@ -30,7 +29,7 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
@abstract Draws interactive rich text.
@discussion Backed by TextKit.
*/
@interface ASTextNode : ASControlNode <ASBaselineLayoutable>
@interface ASTextNode : ASControlNode
/**
@abstract The attributed string to show.

View File

@@ -17,7 +17,6 @@
#import <AsyncDisplayKit/ASTextNodeTextKitHelpers.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import "ASInternalHelpers.h"
#import "ASTextNodeRenderer.h"
#import "ASTextNodeShadower.h"
@@ -108,9 +107,6 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f)
UILongPressGestureRecognizer *_longPressGestureRecognizer;
}
@synthesize ascender = _ascender;
@synthesize descender = _descender;
#pragma mark - NSObject
- (instancetype)init
@@ -153,13 +149,13 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f)
return self;
}
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
{
ASDisplayNodeAssertNotSupported();
return nil;
}
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
{
ASDisplayNodeAssertNotSupported();
return nil;
@@ -359,9 +355,6 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f)
self.isAccessibilityElement = YES;
}
});
_ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * ASScreenScale())/ASScreenScale();
_descender = round([[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender] * ASScreenScale())/ASScreenScale();
}
#pragma mark - Text Layout

View File

@@ -21,6 +21,7 @@
#import <AsyncDisplayKit/ASTableView.h>
#import <AsyncDisplayKit/ASCollectionView.h>
#import <AsyncDisplayKit/ASCollectionNode.h>
#import <AsyncDisplayKit/ASCellNode.h>
#import <AsyncDisplayKit/ASScrollNode.h>

View File

@@ -13,6 +13,7 @@
#import "ASAssert.h"
#import "ASCollectionView.h"
#import "CGRect+ASConvenience.h"
#import "UICollectionViewLayout+ASConvenience.h"
struct ASDirectionalScreenfulBuffer {
CGFloat positiveDirection; // Positive relative to iOS Core Animation layer coordinate space.
@@ -56,7 +57,7 @@ typedef struct ASRangeGeometry ASRangeGeometry;
@interface ASCollectionViewLayoutController ()
{
UIScrollView * __weak _scrollView;
ASCollectionView * __weak _collectionView;
UICollectionViewLayout * __strong _collectionViewLayout;
std::vector<CGRect> _updateRangeBoundsIndexedByRangeType;
ASScrollDirection _scrollableDirections;
@@ -72,7 +73,7 @@ typedef struct ASRangeGeometry ASRangeGeometry;
}
_scrollableDirections = [collectionView scrollableDirections];
_scrollView = collectionView;
_collectionView = collectionView;
_collectionViewLayout = [collectionView collectionViewLayout];
_updateRangeBoundsIndexedByRangeType = std::vector<CGRect>(ASLayoutRangeTypeCount);
return self;
@@ -94,8 +95,13 @@ typedef struct ASRangeGeometry ASRangeGeometry;
- (ASRangeGeometry)rangeGeometryWithScrollDirection:(ASScrollDirection)scrollDirection
rangeTuningParameters:(ASRangeTuningParameters)rangeTuningParameters
{
CGRect rangeBounds = _scrollView.bounds;
CGRect updateBounds = _scrollView.bounds;
CGRect rangeBounds = _collectionView.bounds;
CGRect updateBounds = _collectionView.bounds;
//scrollable directions can change for non-flow layouts
if ([_collectionViewLayout asdk_isFlowLayout] == NO) {
_scrollableDirections = [_collectionView scrollableDirections];
}
BOOL canScrollHorizontally = ASScrollDirectionContainsHorizontalDirection(_scrollableDirections);
if (canScrollHorizontally) {
@@ -148,7 +154,7 @@ typedef struct ASRangeGeometry ASRangeGeometry;
return YES;
}
CGRect currentBounds = _scrollView.bounds;
CGRect currentBounds = _collectionView.bounds;
if (CGRectIsEmpty(currentBounds)) {
currentBounds = CGRectMake(0, 0, viewportSize.width, viewportSize.height);
}

View File

@@ -85,7 +85,7 @@
* End updates.
*
* @param rangeController Sender.
*
* @param animated NO if all animations are disabled. YES otherwise.
* @param completion Completion block.
*/
- (void)rangeController:(ASRangeController * )rangeController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion;
@@ -104,6 +104,8 @@
*
* @param rangeController Sender.
*
* @param nodes Inserted nodes.
*
* @param indexPaths Index path of inserted nodes.
*
* @param animationOptions Animation options. See ASDataControllerAnimationOptions.
@@ -115,6 +117,8 @@
*
* @param rangeController Sender.
*
* @param nodes Deleted nodes.
*
* @param indexPaths Index path of deleted nodes.
*
* @param animationOptions Animation options. See ASDataControllerAnimationOptions.

View File

@@ -39,8 +39,12 @@
[node recursivelySetDisplaySuspended:NO];
// add the node to an off-screen window to force display and preserve its contents
[[self.class workingWindow] addSubnode:node];
// Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile.
// Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations.
// Any view-backed nodes will still create their views in order to assemble the layer heirarchy, and they will
// also assemble a view subtree for the node, but we avoid the much more significant expense triggered by a view
// being added or removed from an onscreen window (responder chain setup, will/DidMoveToWindow: recursive calls, etc)
[[[[self class] workingWindow] layer] addSublayer:node.layer];
}
- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType
@@ -48,9 +52,36 @@
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeRender, @"Render delegate should not handle other ranges");
// This code is tricky. There are several possible states a node can be in when it reaches this point.
// 1. Layer-backed vs view-backed nodes. AS of this writing, only ASCellNodes arrive here, which are always view-backed —
// but we maintain correctness for all ASDisplayNodes, including layer-backed ones.
// (Note: it would not make sense to pass in a subnode of a rasterized node here, so that is unsupported).
// 2. The node's layer may have been added to the workingWindow previously, or it may have never been added, such as if rangeTuningParameter's leading value is 0.
// 3. The node's layer may not be present in the workingWindow, even if it was previously added.
// This is a common case, as once the node is added to an active cell contentsView (e.g. visible), it is automatically removed from the workingWindow.
// The system does this when addSublayer is called, even if removeFromSuperlayer is never explicitly called.
// 4. Lastly and most unusually, it is possible for a node to be offscreen, completely outside the heirarchy, and yet considered within the working range.
// This happens if the UITableViewCell is reused after scrolling offscreen. Because the node has already been given the opportunity to display, we do not
// proactively re-host it within the workingWindow (improving efficiency). Some time later, it may fall outside the working range, in which case calling
// -recursivelyClearContents is critical. If the user scrolls back and it is re-hosted in a UITableViewCell, the content will still exist as it is not cleared
// by simply being removed from the cell. The code that usually triggers this condition is the -removeFromSuperview in -[ASRangeController configureContentView:forCellNode:].
// Condition #4 is suboptimal in some cases, as it is conceivable that memory warnings could trigger clearing content that is inside the working range. However, enforcing the
// preservation of this content could result in the app being killed, which is not likely preferable over briefly seeing placeholders in the event the user scrolls backwards.
// Nonetheless, future changes to the implementation will likely eliminate this behavior to simplify debugging and extensibility of working range functionality.
[node recursivelySetDisplaySuspended:YES];
[node.view removeFromSuperview];
if (node.layer.superlayer != [[[self class] workingWindow] layer]) {
// In this case, the node has previously passed through the working range (or it is zero), and it has now fallen outside the working range.
if (![node isLayerBacked]) {
// If the node is view-backed, we need to make sure to remove the view (which is now present in the containing cell contentsView).
// Layer-backed nodes will be fully handled by the unconditional removal below.
[node.view removeFromSuperview];
}
}
// At this point, the node's layer may validly be present either in the workingWindow, or in the contentsView of a cell.
[node.layer removeFromSuperlayer];
[node recursivelyClearContents];
}

View File

@@ -15,7 +15,6 @@
*/
@interface ASBackgroundLayoutSpec : ASLayoutSpec
@property (nonatomic, strong) id<ASLayoutable> child;
@property (nonatomic, strong) id<ASLayoutable> background;
/**

View File

@@ -14,11 +14,9 @@
#import "ASBaseDefines.h"
#import "ASLayout.h"
static NSString * const kBackgroundChildKey = @"kBackgroundChildKey";
@interface ASBackgroundLayoutSpec ()
{
id<ASLayoutable> _child;
id<ASLayoutable> _background;
}
@end
@implementation ASBackgroundLayoutSpec
@@ -30,12 +28,11 @@
}
ASDisplayNodeAssertNotNil(child, @"Child cannot be nil");
_child = child;
_background = background;
[self setChild:child];
self.background = background;
return self;
}
+ (instancetype)backgroundLayoutSpecWithChild:(id<ASLayoutable>)child background:(id<ASLayoutable>)background;
{
return [[self alloc] initWithChild:child background:background];
@@ -46,12 +43,12 @@
*/
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
{
ASLayout *contentsLayout = [_child measureWithSizeRange:constrainedSize];
ASLayout *contentsLayout = [[self child] measureWithSizeRange:constrainedSize];
NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:2];
if (_background) {
if (self.background) {
// Size background to exactly the same size.
ASLayout *backgroundLayout = [_background measureWithSizeRange:{contentsLayout.size, contentsLayout.size}];
ASLayout *backgroundLayout = [self.background measureWithSizeRange:{contentsLayout.size, contentsLayout.size}];
backgroundLayout.position = CGPointZero;
[sublayouts addObject:backgroundLayout];
}
@@ -63,14 +60,23 @@
- (void)setBackground:(id<ASLayoutable>)background
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
_background = background;
[super setChild:background forIdentifier:kBackgroundChildKey];
}
- (void)setChild:(id<ASLayoutable>)child
- (id<ASLayoutable>)background
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
_child = child;
return [super childForIdentifier:kBackgroundChildKey];
}
- (void)setChildren:(NSArray *)children
{
ASDisplayNodeAssert(NO, @"not supported by this layout spec");
}
- (NSArray *)children
{
ASDisplayNodeAssert(NO, @"not supported by this layout spec");
return nil;
}
@end

View File

@@ -1,63 +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 <AsyncDisplayKit/ASStackLayoutSpec.h>
#import <AsyncDisplayKit/ASBaselineLayoutable.h>
typedef NS_ENUM(NSUInteger, ASBaselineLayoutBaselineAlignment) {
/** No baseline alignment. This is only valid for a vertical stack */
ASBaselineLayoutBaselineAlignmentNone,
/** Align all children to the first baseline. This is only valid for a horizontal stack */
ASBaselineLayoutBaselineAlignmentFirst,
/** Align all children to the last baseline. This is useful when a text node wraps and you want to align
to the bottom baseline. This is only valid for a horizontal stack */
ASBaselineLayoutBaselineAlignmentLast,
};
/**
A specialized version of a stack layout that aligns its children on a baseline. This spec only works with
ASBaselineLayoutable children.
If the spec is created with a horizontal direction, the children will be laid on a common baseline.
If the spec is created with a vertical direction, a child's vertical spacing will be measured from its
baseline instead of from the child's bounding box.
*/
@interface ASBaselineLayoutSpec : ASLayoutSpec <ASBaselineLayoutable>
/** Specifies the direction children are stacked in. */
@property (nonatomic, assign) ASStackLayoutDirection direction;
/** The amount of space between each child. */
@property (nonatomic, assign) CGFloat spacing;
/** The amount of space between each child. */
@property (nonatomic, assign) ASStackLayoutJustifyContent justifyContent;
/** Orientation of children along cross axis */
@property (nonatomic, assign) ASStackLayoutAlignItems alignItems;
/** The type of baseline alignment */
@property (nonatomic, assign) ASBaselineLayoutBaselineAlignment baselineAlignment;
- (void)addChild:(id<ASBaselineLayoutable>)child;
- (void)addChildren:(NSArray *)children;
/**
@param direction The direction of the stack view (horizontal or vertical)
@param spacing The spacing between the children
@param baselineAlignment The baseline to align to
@param justifyContent If no children are flexible, this describes how to fill any extra space
@param alignItems Orientation of the children along the cross axis
@param children ASLayoutable children to be positioned.
*/
+ (instancetype)baselineLayoutSpecWithDirection:(ASStackLayoutDirection)direction
spacing:(CGFloat)spacing
baselineAlignment:(ASBaselineLayoutBaselineAlignment)baselineAlignment
justifyContent:(ASStackLayoutJustifyContent)justifyContent
alignItems:(ASStackLayoutAlignItems)alignItems
children:(NSArray *)children;
@end

View File

@@ -1,97 +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 "ASBaselineLayoutSpec.h"
#import "ASStackLayoutable.h"
#import <numeric>
#import <vector>
#import "ASBaseDefines.h"
#import "ASInternalHelpers.h"
#import "ASLayoutSpecUtilities.h"
#import "ASStackLayoutSpecUtilities.h"
#import "ASStackPositionedLayout.h"
#import "ASStackUnpositionedLayout.h"
#import "ASBaselinePositionedLayout.h"
#import "ASThread.h"
@implementation ASBaselineLayoutSpec
{
std::vector<id<ASStackLayoutable>> _children;
ASDN::RecursiveMutex _propertyLock;
}
@synthesize ascender = _ascender;
@synthesize descender = _descender;
- (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing baselineAlignment:(ASBaselineLayoutBaselineAlignment)baselineAlignment justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children
{
if (!(self = [super init])) {
return nil;
}
ASDisplayNodeAssert((direction == ASStackLayoutDirectionHorizontal && baselineAlignment != ASBaselineLayoutBaselineAlignmentNone) || direction == ASStackLayoutDirectionVertical, @"baselineAlignment is set to none. If you don't need baseline alignment please use ASStackLayoutSpec");
_direction = direction;
_alignItems = alignItems;
_spacing = spacing;
_justifyContent = justifyContent;
_baselineAlignment = baselineAlignment;
_children = std::vector<id<ASStackLayoutable>>();
for (id<ASStackLayoutable> child in children) {
_children.push_back(child);
}
return self;
}
+ (instancetype)baselineLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing baselineAlignment:(ASBaselineLayoutBaselineAlignment)baselineAlignment justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children
{
return [[ASBaselineLayoutSpec alloc] initWithDirection:direction spacing:spacing baselineAlignment:baselineAlignment justifyContent:justifyContent alignItems:alignItems children:children];
}
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
{
ASStackLayoutSpecStyle stackStyle = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems};
ASBaselineLayoutSpecStyle style = { .baselineAlignment = _baselineAlignment, .stackLayoutStyle = stackStyle };
const auto unpositionedLayout = ASStackUnpositionedLayout::compute(_children, stackStyle, constrainedSize);
const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, stackStyle, constrainedSize);
const auto baselinePositionedLayout = ASBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize);
const CGSize finalSize = directionSize(stackStyle.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize);
NSArray *sublayouts = [NSArray arrayWithObjects:&baselinePositionedLayout.sublayouts[0] count:baselinePositionedLayout.sublayouts.size()];
ASDN::MutexLocker l(_propertyLock);
_ascender = baselinePositionedLayout.ascender;
_descender = baselinePositionedLayout.descender;
return [ASLayout layoutWithLayoutableObject:self
size:ASSizeRangeClamp(constrainedSize, finalSize)
sublayouts:sublayouts];
}
- (void)addChild:(id<ASBaselineLayoutable>)child
{
_children.push_back(child);
}
- (void)addChildren:(NSArray *)children
{
for (id<ASBaselineLayoutable> child in children) {
[self addChild:child];
}
}
@end

View File

@@ -1,23 +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 "ASStackLayoutable.h"
@protocol ASBaselineLayoutable <ASStackLayoutable>
/**
* @abstract The distance from the top of the layoutable object to its baseline
*/
@property (nonatomic, readwrite) CGFloat ascender;
/**
* @abstract The distance from the bottom of the layoutable object to its baseline
*/
@property (nonatomic, readwrite) CGFloat descender;
@end

View File

@@ -39,7 +39,6 @@ typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecSizingOptions) {
@property (nonatomic, assign) ASCenterLayoutSpecCenteringOptions centeringOptions;
@property (nonatomic, assign) ASCenterLayoutSpecSizingOptions sizingOptions;
@property (nonatomic, strong) id<ASLayoutable> child;
/**
* Initializer.

View File

@@ -17,7 +17,6 @@
{
ASCenterLayoutSpecCenteringOptions _centeringOptions;
ASCenterLayoutSpecSizingOptions _sizingOptions;
id<ASLayoutable> _child;
}
- (instancetype)initWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions
@@ -30,7 +29,7 @@
ASDisplayNodeAssertNotNil(child, @"Child cannot be nil");
_centeringOptions = centeringOptions;
_sizingOptions = sizingOptions;
_child = child;
[self setChild:child];
return self;
}
@@ -41,12 +40,6 @@
return [[self alloc] initWithCenteringOptions:centeringOptions sizingOptions:sizingOptions child:child];
}
- (void)setChild:(id<ASLayoutable>)child
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
_child = child;
}
- (void)setCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
@@ -71,7 +64,7 @@
(_centeringOptions & ASCenterLayoutSpecCenteringX) != 0 ? 0 : constrainedSize.min.width,
(_centeringOptions & ASCenterLayoutSpecCenteringY) != 0 ? 0 : constrainedSize.min.height,
};
ASLayout *sublayout = [_child measureWithSizeRange:ASSizeRangeMake(minChildSize, constrainedSize.max)];
ASLayout *sublayout = [self.child measureWithSizeRange:ASSizeRangeMake(minChildSize, constrainedSize.max)];
// If we have an undetermined height or width, use the child size to define the layout
// size
@@ -97,4 +90,15 @@
return [ASLayout layoutWithLayoutableObject:self size:size sublayouts:@[sublayout]];
}
- (void)setChildren:(NSArray *)children
{
ASDisplayNodeAssert(NO, @"not supported by this layout spec");
}
- (NSArray *)children
{
ASDisplayNodeAssert(NO, @"not supported by this layout spec");
return nil;
}
@end

View File

@@ -29,7 +29,6 @@
*/
@interface ASInsetLayoutSpec : ASLayoutSpec
@property (nonatomic, strong) id<ASLayoutable> child;
@property (nonatomic, assign) UIEdgeInsets insets;
/**

View File

@@ -19,7 +19,6 @@
@interface ASInsetLayoutSpec ()
{
UIEdgeInsets _insets;
id<ASLayoutable> _child;
}
@end
@@ -50,7 +49,7 @@ static CGFloat centerInset(CGFloat outer, CGFloat inner)
}
ASDisplayNodeAssertNotNil(child, @"Child cannot be nil");
_insets = insets;
_child = child;
[self setChild:child];
return self;
}
@@ -59,12 +58,6 @@ static CGFloat centerInset(CGFloat outer, CGFloat inner)
return [[self alloc] initWithInsets:insets child:child];
}
- (void)setChild:(id<ASLayoutable>)child
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
_child = child;
}
- (void)setInsets:(UIEdgeInsets)insets
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
@@ -95,7 +88,7 @@ static CGFloat centerInset(CGFloat outer, CGFloat inner)
MAX(0, constrainedSize.max.height - insetsY),
}
};
ASLayout *sublayout = [_child measureWithSizeRange:insetConstrainedSize];
ASLayout *sublayout = [self.child measureWithSizeRange:insetConstrainedSize];
const CGSize computedSize = ASSizeRangeClamp(constrainedSize, {
finite(sublayout.size.width + _insets.left + _insets.right, constrainedSize.max.width),
@@ -116,4 +109,15 @@ static CGFloat centerInset(CGFloat outer, CGFloat inner)
return [ASLayout layoutWithLayoutableObject:self size:computedSize sublayouts:@[sublayout]];
}
- (void)setChildren:(NSArray *)children
{
ASDisplayNodeAssert(NO, @"not supported by this layout spec");
}
- (NSArray *)children
{
ASDisplayNodeAssert(NO, @"not supported by this layout spec");
return nil;
}
@end

View File

@@ -0,0 +1,85 @@
/*
* 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>
#import <AsyncDisplayKit/ASLayoutSpec.h>
@protocol ASLayoutable;
/**
* A store for all of the options defined by ASLayoutSpec subclasses. All implementors of ASLayoutable own a
* ASLayoutOptions. When certain layoutSpecs need option values, they are read from this class.
*
* Unless you wish to create a custom layout spec, ASLayoutOptions can largerly be ignored. Instead you can access
* the layout option properties exposed in ASLayoutable directly, which will set the values in ASLayoutOptions
* behind the scenes.
*/
@interface ASLayoutOptions : NSObject <ASStackLayoutable, ASStaticLayoutable, NSCopying>
/**
* Sets the class name for the ASLayoutOptions subclasses that will be created when a node or layoutSpec's options
* are first accessed.
*
* If you create a custom layoutSpec that includes new options, you will want to subclass ASLayoutOptions to add
* the new layout options for your layoutSpec(s). In order to make sure your subclass is created instead of an
* instance of ASLayoutOptions, call setDefaultLayoutOptionsClass: early in app launch (applicationDidFinishLaunching:)
* with your subclass's class.
*
* @param defaultLayoutOptionsClass The class of ASLayoutOptions that will be lazily created for a node or layout spec.
*/
+ (void)setDefaultLayoutOptionsClass:(Class)defaultLayoutOptionsClass;
/**
* @return the Class of ASLayoutOptions that will be created for a node or layoutspec. Defaults to [ASLayoutOptions class];
*/
+ (Class)defaultLayoutOptionsClass;
#pragma mark - Subclasses should implement these!
/**
* Initializes a new ASLayoutOptions using the given layoutable to assign any intrinsic option values.
* This init function sets a sensible default value for each layout option. If you create a subclass of
* ASLayoutOptions, your subclass should do the same.
*
* @param layoutable The layoutable that will own these options. The layoutable will be used to set any intrinsic
* layoutOptions. For example, if the layoutable is an ASTextNode the ascender/descender values will get set.
*
* @return a new instance of ASLayoutOptions
*/
- (instancetype)initWithLayoutable:(id<ASLayoutable>)layoutable;
/**
* Copies the values of layoutOptions into self. This is useful when placing a layoutable inside of another. Consider
* an ASTextNode that you want to align to the baseline by putting it in an ASStackLayoutSpec. Before that, you want
* to inset the ASTextNode by placing it in an ASInsetLayoutSpec. An ASInsetLayoutSpec will not have any information
* about the ASTextNode's ascender/descender unless we copy over the layout options from ASTextNode to ASInsetLayoutSpec.
* This is done automatically and should not need to be called directly. It is listed here to make sure that any
* ASLayoutOptions subclass implements the method.
*
* @param layoutOptions The layoutOptions to copy from
*/
- (void)copyIntoOptions:(ASLayoutOptions *)layoutOptions;
#pragma mark - ASStackLayoutable
@property (nonatomic, readwrite) CGFloat spacingBefore;
@property (nonatomic, readwrite) CGFloat spacingAfter;
@property (nonatomic, readwrite) BOOL flexGrow;
@property (nonatomic, readwrite) BOOL flexShrink;
@property (nonatomic, readwrite) ASRelativeDimension flexBasis;
@property (nonatomic, readwrite) ASStackLayoutAlignSelf alignSelf;
@property (nonatomic, readwrite) CGFloat ascender;
@property (nonatomic, readwrite) CGFloat descender;
#pragma mark - ASStaticLayoutable
@property (nonatomic, readwrite) ASRelativeSizeRange sizeRange;
@property (nonatomic, readwrite) CGPoint layoutPosition;
@end

View File

@@ -0,0 +1,262 @@
/*
* 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 "ASLayoutOptions.h"
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import "ASInternalHelpers.h"
@interface ASLayoutOptions()
{
ASDN::RecursiveMutex _propertyLock;
}
@end
@implementation ASLayoutOptions
@synthesize spacingBefore = _spacingBefore;
@synthesize spacingAfter = _spacingAfter;
@synthesize flexGrow = _flexGrow;
@synthesize flexShrink = _flexShrink;
@synthesize flexBasis = _flexBasis;
@synthesize alignSelf = _alignSelf;
@synthesize ascender = _ascender;
@synthesize descender = _descender;
@synthesize sizeRange = _sizeRange;
@synthesize layoutPosition = _layoutPosition;
static Class gDefaultLayoutOptionsClass = nil;
+ (void)setDefaultLayoutOptionsClass:(Class)defaultLayoutOptionsClass
{
gDefaultLayoutOptionsClass = defaultLayoutOptionsClass;
}
+ (Class)defaultLayoutOptionsClass
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (gDefaultLayoutOptionsClass == nil) {
// If someone is asking for this and it hasn't been customized yet, use the default.
gDefaultLayoutOptionsClass = [ASLayoutOptions class];
}
});
return gDefaultLayoutOptionsClass;
}
- (instancetype)init
{
return [self initWithLayoutable:nil];
}
- (instancetype)initWithLayoutable:(id<ASLayoutable>)layoutable;
{
self = [super init];
if (self) {
self.flexBasis = ASRelativeDimensionUnconstrained;
self.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(CGSizeZero), ASRelativeSizeMakeWithCGSize(CGSizeZero));
self.layoutPosition = CGPointZero;
// The following properties use a default value of 0 which we do not need to assign.
// self.spacingBefore = 0;
// self.spacingAfter = 0;
// self.flexGrow = NO;
// self.flexShrink = NO;
// self.alignSelf = ASStackLayoutAlignSelfAuto;
// self.ascender = 0;
// self.descender = 0;
[self setValuesFromLayoutable:layoutable];
}
return self;
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
ASLayoutOptions *copy = [[[self class] alloc] init];
[copy copyIntoOptions:self];
return copy;
}
- (void)copyIntoOptions:(ASLayoutOptions *)layoutOptions
{
ASDN::MutexLocker l(_propertyLock);
self.flexBasis = layoutOptions.flexBasis;
self.spacingAfter = layoutOptions.spacingAfter;
self.spacingBefore = layoutOptions.spacingBefore;
self.flexGrow = layoutOptions.flexGrow;
self.flexShrink = layoutOptions.flexShrink;
self.ascender = layoutOptions.ascender;
self.descender = layoutOptions.descender;
self.sizeRange = layoutOptions.sizeRange;
self.layoutPosition = layoutOptions.layoutPosition;
}
/**
* Given an id<ASLayoutable>, set up layout options that are intrinsically defined by the layoutable.
*
* While this could be done in the layoutable object itself, moving the logic into the ASLayoutOptions class
* allows a custom spec to set up defaults without needing to alter the layoutable itself. For example,
* image you were creating a custom baseline spec that needed ascender/descender. To assign values automatically
* when a text node's attribute string is set, you would need to subclass ASTextNode and assign the values in the
* override of setAttributeString. However, assigning the defaults in an ASLayoutOptions subclass's
* setValuesFromLayoutable allows you to create a custom spec without the need to create a
* subclass of ASTextNode.
*
* @param layoutable The layoutable object to inspect for default intrinsic layout option values
*/
- (void)setValuesFromLayoutable:(id<ASLayoutable>)layoutable
{
ASDN::MutexLocker l(_propertyLock);
if ([layoutable isKindOfClass:[ASDisplayNode class]]) {
ASDisplayNode *displayNode = (ASDisplayNode *)layoutable;
self.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(displayNode.preferredFrameSize), ASRelativeSizeMakeWithCGSize(displayNode.preferredFrameSize));
if ([layoutable isKindOfClass:[ASTextNode class]]) {
ASTextNode *textNode = (ASTextNode *)layoutable;
NSAttributedString *attributedString = textNode.attributedString;
if (attributedString.length > 0) {
CGFloat screenScale = ASScreenScale();
self.ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale;
self.descender = round([[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale;
}
}
}
}
- (CGFloat)spacingAfter
{
ASDN::MutexLocker l(_propertyLock);
return _spacingAfter;
}
- (void)setSpacingAfter:(CGFloat)spacingAfter
{
ASDN::MutexLocker l(_propertyLock);
_spacingAfter = spacingAfter;
}
- (CGFloat)spacingBefore
{
ASDN::MutexLocker l(_propertyLock);
return _spacingBefore;
}
- (void)setSpacingBefore:(CGFloat)spacingBefore
{
ASDN::MutexLocker l(_propertyLock);
_spacingBefore = spacingBefore;
}
- (BOOL)flexGrow
{
ASDN::MutexLocker l(_propertyLock);
return _flexGrow;
}
- (void)setFlexGrow:(BOOL)flexGrow
{
ASDN::MutexLocker l(_propertyLock);
_flexGrow = flexGrow;
}
- (BOOL)flexShrink
{
ASDN::MutexLocker l(_propertyLock);
return _flexShrink;
}
- (void)setFlexShrink:(BOOL)flexShrink
{
ASDN::MutexLocker l(_propertyLock);
_flexShrink = flexShrink;
}
- (ASRelativeDimension)flexBasis
{
ASDN::MutexLocker l(_propertyLock);
return _flexBasis;
}
- (void)setFlexBasis:(ASRelativeDimension)flexBasis
{
ASDN::MutexLocker l(_propertyLock);
_flexBasis = flexBasis;
}
- (ASStackLayoutAlignSelf)alignSelf
{
ASDN::MutexLocker l(_propertyLock);
return _alignSelf;
}
- (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf
{
ASDN::MutexLocker l(_propertyLock);
_alignSelf = alignSelf;
}
- (CGFloat)ascender
{
ASDN::MutexLocker l(_propertyLock);
return _ascender;
}
- (void)setAscender:(CGFloat)ascender
{
ASDN::MutexLocker l(_propertyLock);
_ascender = ascender;
}
- (CGFloat)descender
{
ASDN::MutexLocker l(_propertyLock);
return _descender;
}
- (void)setDescender:(CGFloat)descender
{
ASDN::MutexLocker l(_propertyLock);
_descender = descender;
}
- (ASRelativeSizeRange)sizeRange
{
ASDN::MutexLocker l(_propertyLock);
return _sizeRange;
}
- (void)setSizeRange:(ASRelativeSizeRange)sizeRange
{
ASDN::MutexLocker l(_propertyLock);
_sizeRange = sizeRange;
}
- (CGPoint)layoutPosition
{
ASDN::MutexLocker l(_propertyLock);
return _layoutPosition;
}
- (void)setLayoutPosition:(CGPoint)layoutPosition
{
ASDN::MutexLocker l(_propertyLock);
_layoutPosition = layoutPosition;
}
@end

View File

@@ -0,0 +1,144 @@
/*
* 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 "ASLayoutOptionsPrivate.h"
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import "ASThread.h"
/**
* Both an ASDisplayNode and an ASLayoutSpec conform to ASLayoutable. There are several properties
* in ASLayoutable that are used as layoutOptions when a node or spec is used in a layout spec.
* These properties are provided for convenience, as they are forwards to the node or spec's
* ASLayoutOptions class. Instead of duplicating the property forwarding in both classes, we
* create a define that allows us to easily implement the forwards in one place.
*
* If you create a custom layout spec, we recommend this stragety if you decide to extend
* ASDisplayNode and ASLAyoutSpec to provide convenience properties for any options that your
* layoutSpec may require.
*/
#define ASLayoutOptionsForwarding \
- (ASLayoutOptions *)layoutOptions\
{\
ASDN::MutexLocker l(_layoutOptionsLock);\
if (_layoutOptions == nil) {\
_layoutOptions = [[[ASLayoutOptions defaultLayoutOptionsClass] alloc] initWithLayoutable:self];\
}\
return _layoutOptions;\
}\
\
- (CGFloat)spacingBefore\
{\
return self.layoutOptions.spacingBefore;\
}\
\
- (void)setSpacingBefore:(CGFloat)spacingBefore\
{\
self.layoutOptions.spacingBefore = spacingBefore;\
}\
\
- (CGFloat)spacingAfter\
{\
return self.layoutOptions.spacingAfter;\
}\
\
- (void)setSpacingAfter:(CGFloat)spacingAfter\
{\
self.layoutOptions.spacingAfter = spacingAfter;\
}\
\
- (BOOL)flexGrow\
{\
return self.layoutOptions.flexGrow;\
}\
\
- (void)setFlexGrow:(BOOL)flexGrow\
{\
self.layoutOptions.flexGrow = flexGrow;\
}\
\
- (BOOL)flexShrink\
{\
return self.layoutOptions.flexShrink;\
}\
\
- (void)setFlexShrink:(BOOL)flexShrink\
{\
self.layoutOptions.flexShrink = flexShrink;\
}\
\
- (ASRelativeDimension)flexBasis\
{\
return self.layoutOptions.flexBasis;\
}\
\
- (void)setFlexBasis:(ASRelativeDimension)flexBasis\
{\
self.layoutOptions.flexBasis = flexBasis;\
}\
\
- (ASStackLayoutAlignSelf)alignSelf\
{\
return self.layoutOptions.alignSelf;\
}\
\
- (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf\
{\
self.layoutOptions.alignSelf = alignSelf;\
}\
\
- (CGFloat)ascender\
{\
return self.layoutOptions.ascender;\
}\
\
- (void)setAscender:(CGFloat)ascender\
{\
self.layoutOptions.ascender = ascender;\
}\
\
- (CGFloat)descender\
{\
return self.layoutOptions.descender;\
}\
\
- (void)setDescender:(CGFloat)descender\
{\
self.layoutOptions.descender = descender;\
}\
\
- (ASRelativeSizeRange)sizeRange\
{\
return self.layoutOptions.sizeRange;\
}\
\
- (void)setSizeRange:(ASRelativeSizeRange)sizeRange\
{\
self.layoutOptions.sizeRange = sizeRange;\
}\
\
- (CGPoint)layoutPosition\
{\
return self.layoutOptions.layoutPosition;\
}\
\
- (void)setLayoutPosition:(CGPoint)position\
{\
self.layoutOptions.layoutPosition = position;\
}\
@implementation ASDisplayNode(ASLayoutOptions)
ASLayoutOptionsForwarding
@end
@implementation ASLayoutSpec(ASLayoutOptions)
ASLayoutOptionsForwarding
@end

View File

@@ -8,18 +8,91 @@
*
*/
#import <AsyncDisplayKit/ASStackLayoutable.h>
#import <AsyncDisplayKit/ASLayoutable.h>
/** A layout spec is an immutable object that describes a layout, loosely inspired by React. */
@interface ASLayoutSpec : NSObject <ASStackLayoutable>
@interface ASLayoutSpec : NSObject <ASLayoutable>
/**
Creation of a layout spec should only happen by a user in layoutSpecThatFits:. During that method, a
layout spec can be created and mutated. Once it is passed back to ASDK, the isMutable flag will be
set to NO and any further mutations will cause an assert.
* Creation of a layout spec should only happen by a user in layoutSpecThatFits:. During that method, a
* layout spec can be created and mutated. Once it is passed back to ASDK, the isMutable flag will be
* set to NO and any further mutations will cause an assert.
*/
@property (nonatomic, assign) BOOL isMutable;
- (instancetype)init;
/**
* Adds a child to this layout spec using a default identifier.
*
* @param child A child to be added.
*
* @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the
* reponsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec,
* only require a single child.
*
* For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example)
* a subclass should use this method to set the "primary" child. It can then use setChild:forIdentifier:
* to set any other required children. Ideally a subclass would hide this from the user, and use the
* setChild:forIdentifier: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild
* property that behind the scenes is calling setChild:forIdentifier:.
*/
- (void)setChild:(id<ASLayoutable>)child;
/**
* Adds a child with the given identifier to this layout spec.
*
* @param child A child to be added.
*
* @param identifier An identifier associated with the child.
*
* @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the
* reponsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec,
* only require a single child.
*
* For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example)
* a subclass should use the setChild method to set the "primary" child. It can then use this method
* to set any other required children. Ideally a subclass would hide this from the user, and use the
* setChild:forIdentifier: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild
* property that behind the scenes is calling setChild:forIdentifier:.
*/
- (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier;
/**
* Adds childen to this layout spec.
*
* @param children An array of ASLayoutable children to be added.
*
* @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the
* reponsibility of holding on to the spec children. Some layout specs, like ASStackLayoutSpec,
* can take an unknown number of children. In this case, the this method should be used.
* For good measure, in these layout specs it probably makes sense to define
* setChild: and setChild:forIdentifier: methods to do something appropriate or to assert.
*/
- (void)setChildren:(NSArray *)children;
/**
* Get child methods
*
* There is a corresponding "getChild" method for the above "setChild" methods. If a subclass
* has extra layoutable children, it is recommended to make a corresponding get method for that
* child. For example, the ASBackgroundLayoutSpec responds to backgroundChild.
*
* If a get method is called on a spec that doesn't make sense, then the standard is to assert.
* For example, calling children on an ASInsetLayoutSpec will assert.
*/
/** Returns the child added to this layout spec using the default identifier. */
- (id<ASLayoutable>)child;
/**
* Returns the child added to this layout spec using the given identifier.
*
* @param identifier An identifier associated withe the child.
*/
- (id<ASLayoutable>)childForIdentifier:(NSString *)identifier;
/** Returns all children added to this layout spec. */
- (NSArray *)children;
@end

View File

@@ -15,22 +15,32 @@
#import "ASInternalHelpers.h"
#import "ASLayout.h"
#import "ASLayoutOptions.h"
#import "ASLayoutOptionsPrivate.h"
#import "ASThread.h"
#import <objc/runtime.h>
static NSString * const kDefaultChildKey = @"kDefaultChildKey";
static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey";
@interface ASLayoutSpec()
@property (nonatomic, strong) NSMutableDictionary *layoutChildren;
@end
@implementation ASLayoutSpec
@synthesize spacingBefore = _spacingBefore;
@synthesize spacingAfter = _spacingAfter;
@synthesize flexGrow = _flexGrow;
@synthesize flexShrink = _flexShrink;
@synthesize flexBasis = _flexBasis;
@synthesize alignSelf = _alignSelf;
// these dynamic properties all defined in ASLayoutOptionsPrivate.m
@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition, layoutOptions;
@synthesize layoutChildren = _layoutChildren;
@synthesize isFinalLayoutable = _isFinalLayoutable;
- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}
_flexBasis = ASRelativeDimensionUnconstrained;
_layoutChildren = [NSMutableDictionary dictionary];
_isMutable = YES;
return self;
}
@@ -42,4 +52,75 @@
return [ASLayout layoutWithLayoutableObject:self size:constrainedSize.min];
}
- (id<ASLayoutable>)finalLayoutable
{
return self;
}
- (void)setChild:(id<ASLayoutable>)child;
{
[self setChild:child forIdentifier:kDefaultChildKey];
}
- (id<ASLayoutable>)layoutableToAddFromLayoutable:(id<ASLayoutable>)child
{
if (self.isFinalLayoutable == NO) {
// If you are getting recursion crashes here after implementing finalLayoutable, make sure
// that you are setting isFinalLayoutable flag to YES. This must be one BEFORE adding a child
// to the new ASLayoutable.
//
// For example:
//- (id<ASLayoutable>)finalLayoutable
//{
// ASInsetLayoutSpec *insetSpec = [[ASInsetLayoutSpec alloc] init];
// insetSpec.insets = UIEdgeInsetsMake(10,10,10,10);
// insetSpec.isFinalLayoutable = YES;
// [insetSpec setChild:self];
// return insetSpec;
//}
id<ASLayoutable> finalLayoutable = [child finalLayoutable];
if (finalLayoutable != child) {
[finalLayoutable.layoutOptions copyIntoOptions:child.layoutOptions];
return finalLayoutable;
}
}
return child;
}
- (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
self.layoutChildren[identifier] = [self layoutableToAddFromLayoutable:child];;
}
- (void)setChildren:(NSArray *)children
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
NSMutableArray *finalChildren = [NSMutableArray arrayWithCapacity:children.count];
for (id<ASLayoutable> child in children) {
[finalChildren addObject:[self layoutableToAddFromLayoutable:child]];
}
self.layoutChildren[kDefaultChildrenKey] = [NSArray arrayWithArray:finalChildren];
}
- (id<ASLayoutable>)childForIdentifier:(NSString *)identifier
{
return self.layoutChildren[identifier];
}
- (id<ASLayoutable>)child
{
return self.layoutChildren[kDefaultChildKey];
}
- (NSArray *)children
{
return self.layoutChildren[kDefaultChildrenKey];
}
@end

View File

@@ -9,16 +9,33 @@
*/
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASRelativeSize.h>
#import <AsyncDisplayKit/ASStackLayoutDefines.h>
#import <AsyncDisplayKit/ASStackLayoutable.h>
#import <AsyncDisplayKit/ASStaticLayoutable.h>
#import <AsyncDisplayKit/ASLayoutablePrivate.h>
@class ASLayout;
@class ASLayoutSpec;
/**
* The ASLayoutable protocol declares a method for measuring the layout of an object. A class must implement the method
* so that instances of that class can be used to build layout trees. The protocol also provides information
* about how an object should be laid out within an ASStackLayoutSpec.
* The ASLayoutable protocol declares a method for measuring the layout of an object. A layout
* is defined by an ASLayout return value, and must specify 1) the size (but not position) of the
* layoutable object, and 2) the size and position of all of its immediate child objects. The tree
* recursion is driven by parents requesting layouts from their children in order to determine their
* size, followed by the parents setting the position of the children once the size is known
*
* The protocol also implements a "family" of Layoutable protocols. These protocols contain layout
* options that can be used for specific layout specs. For example, ASStackLayoutSpec has options
* defining how a layoutable should shrink or grow based upon available space.
*
* These layout options are all stored in an ASLayoutOptions class (that is defined in ASLayoutablePrivate).
* Generally you needn't worry about the layout options class, as the layoutable protocols allow all direct
* access to the options via convenience properties. If you are creating custom layout spec, then you can
* extend the backing layout options class to accomodate any new layout options.
*/
@protocol ASLayoutable <NSObject>
@protocol ASLayoutable <ASStackLayoutable, ASStaticLayoutable, ASLayoutablePrivate>
/**
* @abstract Calculate a layout based on given size range.
@@ -29,4 +46,64 @@
*/
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize;
#pragma mark - Layout options from the Layoutable Protocols
#pragma mark - ASStackLayoutable
/**
* @abstract Additional space to place before this object in the stacking direction.
* Used when attached to a stack layout.
*/
@property (nonatomic, readwrite) CGFloat spacingBefore;
/**
* @abstract Additional space to place after this object in the stacking direction.
* Used when attached to a stack layout.
*/
@property (nonatomic, readwrite) CGFloat spacingAfter;
/**
* @abstract If the sum of childrens' stack dimensions is less than the minimum size, should this object grow?
* Used when attached to a stack layout.
*/
@property (nonatomic, readwrite) BOOL flexGrow;
/**
* @abstract If the sum of childrens' stack dimensions is greater than the maximum size, should this object shrink?
* Used when attached to a stack layout.
*/
@property (nonatomic, readwrite) BOOL flexShrink;
/**
* @abstract Specifies the initial size in the stack dimension for this object.
* Default to ASRelativeDimensionUnconstrained.
* Used when attached to a stack layout.
*/
@property (nonatomic, readwrite) ASRelativeDimension flexBasis;
/**
* @abstract Orientation of the object along cross axis, overriding alignItems
* Used when attached to a stack layout.
*/
@property (nonatomic, readwrite) ASStackLayoutAlignSelf alignSelf;
/**
* @abstract Used for baseline alignment. The distance from the top of the object to its baseline.
*/
@property (nonatomic, readwrite) CGFloat ascender;
/**
* @abstract Used for baseline alignment. The distance from the baseline of the object to its bottom.
*/
@property (nonatomic, readwrite) CGFloat descender;
#pragma mark - ASStaticLayoutable
/**
If specified, the child's size is restricted according to this size. Percentages are resolved relative to the static layout spec.
*/
@property (nonatomic, assign) ASRelativeSizeRange sizeRange;
/** The position of this object within its parent spec. */
@property (nonatomic, assign) CGPoint layoutPosition;
@end

View File

@@ -0,0 +1,47 @@
/*
* 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>
@class ASLayoutSpec;
@class ASLayoutOptions;
@protocol ASLayoutable;
/**
* The base protocol for ASLayoutable. Generally the methods/properties in this class do not need to be
* called by the end user and are only called internally. However, there may be a case where the methods are useful.
*/
@protocol ASLayoutablePrivate <NSObject>
/**
* @abstract This method can be used to give the user a chance to wrap an ASLayoutable in an ASLayoutSpec
* just before it is added to a parent ASLayoutSpec. For example, if you wanted an ASTextNode that was always
* inside of an ASInsetLayoutSpec, you could subclass ASTextNode and implement finalLayoutable so that it wraps
* itself in an inset spec.
*
* Note that any ASLayoutable other than self that is returned MUST set isFinalLayoutable to YES. Make sure
* to do this BEFORE adding a child to the ASLayoutable.
*
* @return The layoutable that will be added to the parent layout spec. Defaults to self.
*/
- (id<ASLayoutable>)finalLayoutable;
/**
* A flag to indicate that this ASLayoutable was created in finalLayoutable. This MUST be set to YES
* before adding a child to this layoutable.
*/
@property (nonatomic, assign) BOOL isFinalLayoutable;
/**
* The class that holds all of the layoutOptions set on an ASLayoutable.
*/
@property (nonatomic, strong, readonly) ASLayoutOptions *layoutOptions;
@end

View File

@@ -15,7 +15,6 @@
*/
@interface ASOverlayLayoutSpec : ASLayoutSpec
@property (nonatomic, strong) id<ASLayoutable> child;
@property (nonatomic, strong) id<ASLayoutable> overlay;
+ (instancetype)overlayLayoutSpecWithChild:(id<ASLayoutable>)child overlay:(id<ASLayoutable>)overlay;

View File

@@ -14,11 +14,9 @@
#import "ASBaseDefines.h"
#import "ASLayout.h"
static NSString * const kOverlayChildKey = @"kOverlayChildKey";
@implementation ASOverlayLayoutSpec
{
id<ASLayoutable> _overlay;
id<ASLayoutable> _child;
}
- (instancetype)initWithChild:(id<ASLayoutable>)child overlay:(id<ASLayoutable>)overlay
{
@@ -26,8 +24,8 @@
return nil;
}
ASDisplayNodeAssertNotNil(child, @"Child that will be overlayed on shouldn't be nil");
_overlay = overlay;
_child = child;
self.overlay = overlay;
[self setChild:child];
return self;
}
@@ -36,16 +34,14 @@
return [[self alloc] initWithChild:child overlay:overlay];
}
- (void)setChild:(id<ASLayoutable>)child
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
_child = child;
}
- (void)setOverlay:(id<ASLayoutable>)overlay
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
_overlay = overlay;
[super setChild:overlay forIdentifier:kOverlayChildKey];
}
- (id<ASLayoutable>)overlay
{
return [super childForIdentifier:kOverlayChildKey];
}
/**
@@ -53,11 +49,11 @@
*/
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
{
ASLayout *contentsLayout = [_child measureWithSizeRange:constrainedSize];
ASLayout *contentsLayout = [self.child measureWithSizeRange:constrainedSize];
contentsLayout.position = CGPointZero;
NSMutableArray *sublayouts = [NSMutableArray arrayWithObject:contentsLayout];
if (_overlay) {
ASLayout *overlayLayout = [_overlay measureWithSizeRange:{contentsLayout.size, contentsLayout.size}];
if (self.overlay) {
ASLayout *overlayLayout = [self.overlay measureWithSizeRange:{contentsLayout.size, contentsLayout.size}];
overlayLayout.position = CGPointZero;
[sublayouts addObject:overlayLayout];
}
@@ -65,4 +61,15 @@
return [ASLayout layoutWithLayoutableObject:self size:contentsLayout.size sublayouts:sublayouts];
}
- (void)setChildren:(NSArray *)children
{
ASDisplayNodeAssert(NO, @"not supported by this layout spec");
}
- (NSArray *)children
{
ASDisplayNodeAssert(NO, @"not supported by this layout spec");
return nil;
}
@end

View File

@@ -31,8 +31,6 @@
**/
@interface ASRatioLayoutSpec : ASLayoutSpec
@property (nonatomic, strong) id<ASLayoutable> child;
@property (nonatomic, assign) CGFloat ratio;
+ (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id<ASLayoutable>)child;

View File

@@ -22,7 +22,6 @@
@implementation ASRatioLayoutSpec
{
CGFloat _ratio;
id<ASLayoutable> _child;
}
+ (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id<ASLayoutable>)child
@@ -38,16 +37,10 @@
ASDisplayNodeAssertNotNil(child, @"Child cannot be nil");
ASDisplayNodeAssert(ratio > 0, @"Ratio should be strictly positive, but received %f", ratio);
_ratio = ratio;
_child = child;
[self setChild:child];
return self;
}
- (void)setChild:(id<ASLayoutable>)child
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
_child = child;
}
- (void)setRatio:(CGFloat)ratio
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
@@ -77,9 +70,20 @@
// If there is no max size in *either* dimension, we can't apply the ratio, so just pass our size range through.
const ASSizeRange childRange = (bestSize == sizeOptions.end()) ? constrainedSize : ASSizeRangeMake(*bestSize, *bestSize);
ASLayout *sublayout = [_child measureWithSizeRange:childRange];
ASLayout *sublayout = [self.child measureWithSizeRange:childRange];
sublayout.position = CGPointZero;
return [ASLayout layoutWithLayoutableObject:self size:sublayout.size sublayouts:@[sublayout]];
}
- (void)setChildren:(NSArray *)children
{
ASDisplayNodeAssert(NO, @"not supported by this layout spec");
}
- (NSArray *)children
{
ASDisplayNodeAssert(NO, @"not supported by this layout spec");
return nil;
}
@end

View File

@@ -44,7 +44,11 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignItems) {
/** Center children on cross axis */
ASStackLayoutAlignItemsCenter,
/** Expand children to fill cross axis */
ASStackLayoutAlignItemsStretch
ASStackLayoutAlignItemsStretch,
/** Children align to their first baseline. Only available for horizontal stack nodes */
ASStackLayoutAlignItemsBaselineFirst,
/** Children align to their last baseline. Only available for horizontal stack nodes */
ASStackLayoutAlignItemsBaselineLast,
};
/**
@@ -62,4 +66,8 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignSelf) {
ASStackLayoutAlignSelfCenter,
/** Expand to fill cross axis */
ASStackLayoutAlignSelfStretch,
/** Children align to their first baseline. Only available for horizontal stack nodes */
ASStackLayoutAlignSelfBaselineFirst,
/** Children align to their last baseline. Only available for horizontal stack nodes */
ASStackLayoutAlignSelfBaselineLast,
};

View File

@@ -43,6 +43,8 @@
@property (nonatomic, assign) ASStackLayoutJustifyContent justifyContent;
/** Orientation of children along cross axis */
@property (nonatomic, assign) ASStackLayoutAlignItems alignItems;
/** If YES the vertical spacing between two views is measured from the last baseline of the top view to the top of the bottom view */
@property (nonatomic, assign) BOOL baselineRelativeArrangement;
- (instancetype)init;
@@ -55,7 +57,4 @@
*/
+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children;
- (void)addChild:(id<ASStackLayoutable>)child;
- (void)addChildren:(NSArray *)children;
@end

View File

@@ -17,6 +17,7 @@
#import "ASInternalHelpers.h"
#import "ASLayoutSpecUtilities.h"
#import "ASStackBaselinePositionedLayout.h"
#import "ASStackLayoutSpecUtilities.h"
#import "ASStackPositionedLayout.h"
#import "ASStackUnpositionedLayout.h"
@@ -24,7 +25,7 @@
@implementation ASStackLayoutSpec
{
std::vector<id<ASStackLayoutable>> _children;
ASDN::RecursiveMutex _propertyLock;
}
- (instancetype)init
@@ -47,26 +48,10 @@
_spacing = spacing;
_justifyContent = justifyContent;
_children = std::vector<id<ASStackLayoutable>>();
for (id<ASStackLayoutable> child in children) {
_children.push_back(child);
}
[self setChildren:children];
return self;
}
- (void)addChild:(id<ASStackLayoutable>)child
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
_children.push_back(child);
}
- (void)addChildren:(NSArray *)children
{
for (id<ASStackLayoutable> child in children) {
[self addChild:child];
}
}
- (void)setDirection:(ASStackLayoutDirection)direction
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
@@ -91,13 +76,52 @@
_spacing = spacing;
}
- (void)setBaselineRelativeArrangement:(BOOL)baselineRelativeArrangement
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
_baselineRelativeArrangement = baselineRelativeArrangement;
}
- (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier
{
ASDisplayNodeAssert(NO, @"ASStackLayoutSpec only supports setChildren");
}
- (id<ASLayoutable>)childForIdentifier:(NSString *)identifier
{
ASDisplayNodeAssert(NO, @"ASStackLayoutSpec only supports children");
return nil;
}
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
{
ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems};
const auto unpositionedLayout = ASStackUnpositionedLayout::compute(_children, style, constrainedSize);
ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .baselineRelativeArrangement = _baselineRelativeArrangement};
BOOL needsBaselinePass = _baselineRelativeArrangement || _alignItems == ASStackLayoutAlignItemsBaselineFirst || _alignItems == ASStackLayoutAlignItemsBaselineLast;
std::vector<id<ASLayoutable>> stackChildren = std::vector<id<ASLayoutable>>();
for (id<ASLayoutable> child in self.children) {
stackChildren.push_back(child);
needsBaselinePass |= child.alignSelf == ASStackLayoutAlignSelfBaselineFirst || child.alignSelf == ASStackLayoutAlignSelfBaselineLast;
}
const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize);
const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize);
const CGSize finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, positionedLayout.crossSize);
NSArray *sublayouts = [NSArray arrayWithObjects:&positionedLayout.sublayouts[0] count:positionedLayout.sublayouts.size()];
CGSize finalSize = CGSizeZero;
NSArray *sublayouts = nil;
if (needsBaselinePass) {
const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize);
ASDN::MutexLocker l(_propertyLock);
self.ascender = baselinePositionedLayout.ascender;
self.descender = baselinePositionedLayout.descender;
finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize);
sublayouts = [NSArray arrayWithObjects:&baselinePositionedLayout.sublayouts[0] count:baselinePositionedLayout.sublayouts.size()];
} else {
finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, positionedLayout.crossSize);
sublayouts = [NSArray arrayWithObjects:&positionedLayout.sublayouts[0] count:positionedLayout.sublayouts.size()];
}
return [ASLayout layoutWithLayoutableObject:self
size:ASSizeRangeClamp(constrainedSize, finalSize)
sublayouts:sublayouts];

View File

@@ -8,9 +8,13 @@
*
*/
#import <AsyncDisplayKit/ASLayoutable.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASStackLayoutDefines.h>
@protocol ASStackLayoutable <ASLayoutable>
/**
* Layout options that can be defined for an ASLayoutable being added to a ASStackLayoutSpec.
*/
@protocol ASStackLayoutable <NSObject>
/**
* @abstract Additional space to place before this object in the stacking direction.
@@ -49,4 +53,14 @@
*/
@property (nonatomic, readwrite) ASStackLayoutAlignSelf alignSelf;
/**
* @abstract Used for baseline alignment. The distance from the top of the object to its baseline.
*/
@property (nonatomic, readwrite) CGFloat ascender;
/**
* @abstract Used for baseline alignment. The distance from the baseline of the object to its bottom.
*/
@property (nonatomic, readwrite) CGFloat descender;
@end

View File

@@ -11,43 +11,6 @@
#import <AsyncDisplayKit/ASLayoutSpec.h>
#import <AsyncDisplayKit/ASRelativeSize.h>
/**
* An ASStaticLayoutSpecChild object wraps an ASLayoutable object and provides position and size information,
* to be used as a child of an ASStaticLayoutSpec.
*/
@interface ASStaticLayoutSpecChild : NSObject
@property (nonatomic, readonly) CGPoint position;
@property (nonatomic, readonly) id<ASLayoutable> layoutableObject;
/**
If specified, the child's size is restricted according to this size. Percentages are resolved relative to the static layout spec.
*/
@property (nonatomic, readonly) ASRelativeSizeRange size;
/**
* Initializer.
*
* @param position The position of this child within its parent spec.
*
* @param layoutableObject The backing ASLayoutable object of this child.
*
* @param size The size range that this child's size is trstricted according to.
*/
+ (instancetype)staticLayoutChildWithPosition:(CGPoint)position layoutableObject:(id<ASLayoutable>)layoutableObject size:(ASRelativeSizeRange)size;
/**
* Convenience initializer with default size is Unconstrained in both dimensions, which sets the child's min size to zero
* and max size to the maximum available space it can consume without overflowing the spec's bounds.
*
* @param position The position of this child within its parent spec.
*
* @param layoutableObject The backing ASLayoutable object of this child.
*/
+ (instancetype)staticLayoutChildWithPosition:(CGPoint)position layoutableObject:(id<ASLayoutable>)layoutableObject;
@end
/**
* A layout spec that positions children at fixed positions.
*
@@ -56,10 +19,8 @@
@interface ASStaticLayoutSpec : ASLayoutSpec
/**
@param children Children to be positioned at fixed positions, each is of type ASStaticLayoutSpecChild.
@param children Children to be positioned at fixed positions, each conforms to ASStaticLayoutable
*/
+ (instancetype)staticLayoutSpecWithChildren:(NSArray *)children;
- (void)addChild:(ASStaticLayoutSpecChild *)child;
@end

View File

@@ -11,54 +11,32 @@
#import "ASStaticLayoutSpec.h"
#import "ASLayoutSpecUtilities.h"
#import "ASLayoutOptions.h"
#import "ASInternalHelpers.h"
#import "ASLayout.h"
@implementation ASStaticLayoutSpecChild
+ (instancetype)staticLayoutChildWithPosition:(CGPoint)position layoutableObject:(id<ASLayoutable>)layoutableObject size:(ASRelativeSizeRange)size;
{
ASStaticLayoutSpecChild *c = [[super alloc] init];
if (c) {
c->_position = position;
c->_layoutableObject = layoutableObject;
c->_size = size;
}
return c;
}
+ (instancetype)staticLayoutChildWithPosition:(CGPoint)position layoutableObject:(id<ASLayoutable>)layoutableObject
{
return [self staticLayoutChildWithPosition:position layoutableObject:layoutableObject size:ASRelativeSizeRangeUnconstrained];
}
@end
#import "ASStaticLayoutable.h"
@implementation ASStaticLayoutSpec
{
NSArray *_children;
}
+ (instancetype)staticLayoutSpecWithChildren:(NSArray *)children
{
return [[self alloc] initWithChildren:children];
}
- (instancetype)init
{
return [self initWithChildren:@[]];
}
- (instancetype)initWithChildren:(NSArray *)children
{
if (!(self = [super init])) {
return nil;
}
_children = children;
self.children = children;
return self;
}
- (void)addChild:(ASStaticLayoutSpecChild *)child
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
_children = [_children arrayByAddingObject:child];
}
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
{
CGSize size = {
@@ -66,17 +44,17 @@
constrainedSize.max.height
};
NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:_children.count];
for (ASStaticLayoutSpecChild *child in _children) {
NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:self.children.count];
for (id<ASLayoutable> child in self.children) {
CGSize autoMaxSize = {
constrainedSize.max.width - child.position.x,
constrainedSize.max.height - child.position.y
constrainedSize.max.width - child.layoutPosition.x,
constrainedSize.max.height - child.layoutPosition.y
};
ASSizeRange childConstraint = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, child.size)
ASSizeRange childConstraint = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, child.sizeRange)
? ASSizeRangeMake({0, 0}, autoMaxSize)
: ASRelativeSizeRangeResolve(child.size, size);
ASLayout *sublayout = [child.layoutableObject measureWithSizeRange:childConstraint];
sublayout.position = child.position;
: ASRelativeSizeRangeResolve(child.sizeRange, size);
ASLayout *sublayout = [child measureWithSizeRange:childConstraint];
sublayout.position = child.layoutPosition;
[sublayouts addObject:sublayout];
}
@@ -99,4 +77,15 @@
sublayouts:sublayouts];
}
- (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier
{
ASDisplayNodeAssert(NO, @"ASStackLayoutSpec only supports setChildren");
}
- (id<ASLayoutable>)childForIdentifier:(NSString *)identifier
{
ASDisplayNodeAssert(NO, @"ASStackLayoutSpec only supports children");
return nil;
}
@end

View File

@@ -0,0 +1,26 @@
/*
* 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 <AsyncDisplayKit/ASRelativeSize.h>
/**
* Layout options that can be defined for an ASLayoutable being added to a ASStaticLayoutSpec.
*/
@protocol ASStaticLayoutable
/**
If specified, the child's size is restricted according to this size. Percentages are resolved relative to the static layout spec.
*/
@property (nonatomic, assign) ASRelativeSizeRange sizeRange;
/** The position of this object within its parent spec. */
@property (nonatomic, assign) CGPoint layoutPosition;
@end

View File

@@ -83,6 +83,13 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
if (self.isHidden || self.alpha <= 0.0) {
return;
}
BOOL rasterizingFromAscendent = [self __rasterizedContainerNode] != nil;
// if super node is rasterizing descendents, subnodes will not have had layout calls becase they don't have layers
if (rasterizingFromAscendent) {
[self __layout];
}
// Capture these outside the display block so they are retained.
UIColor *backgroundColor = self.backgroundColor;
@@ -121,6 +128,11 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
CGContextTranslateCTM(context, frame.origin.x, frame.origin.y);
//support cornerRadius
if (rasterizingFromAscendent && self.cornerRadius && self.clipsToBounds) {
[[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:self.cornerRadius] addClip];
}
// Fill background if any.
CGColorRef backgroundCGColor = backgroundColor.CGColor;
if (backgroundColor && CGColorGetAlpha(backgroundCGColor) > 0.0) {

View File

@@ -17,6 +17,7 @@
#import "ASDisplayNode.h"
#import "ASSentinel.h"
#import "ASThread.h"
#import "ASLayoutOptions.h"
BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector);
void ASDisplayNodePerformBlockOnMainThread(void (^block)());
@@ -59,6 +60,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
ASDisplayNodeViewBlock _viewBlock;
ASDisplayNodeLayerBlock _layerBlock;
ASDisplayNodeDidLoadBlock _nodeLoadedBlock;
Class _viewClass;
Class _layerClass;
UIView *_view;
@@ -72,7 +74,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
_ASPendingState *_pendingViewState;
struct {
struct ASDisplayNodeFlags {
// public properties
unsigned synchronous:1;
unsigned layerBacked:1;

View File

@@ -15,6 +15,7 @@
ASDISPLAYNODE_EXTERN_C_BEGIN
BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector);
BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL selector);
CGFloat ASScreenScale();

View File

@@ -24,6 +24,15 @@ BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector)
return (superclassIMP != subclassIMP);
}
BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL selector)
{
Method superclassMethod = class_getClassMethod(superclass, selector);
Method subclassMethod = class_getClassMethod(subclass, selector);
IMP superclassIMP = superclassMethod ? method_getImplementation(superclassMethod) : NULL;
IMP subclassIMP = subclassMethod ? method_getImplementation(subclassMethod) : NULL;
return (superclassIMP != subclassIMP);
}
static void ASDispatchOnceOnMainThread(dispatch_once_t *predicate, dispatch_block_t block)
{
if ([NSThread isMainThread]) {

View File

@@ -0,0 +1,33 @@
/*
* 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 <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASLayoutSpec.h>
#import <AsyncDisplayKit/ASThread.h>
@interface ASDisplayNode(ASLayoutOptions)<ASLayoutable>
@end
@interface ASDisplayNode()
{
ASLayoutOptions *_layoutOptions;
ASDN::RecursiveMutex _layoutOptionsLock;
}
@end
@interface ASLayoutSpec(ASLayoutOptions)<ASLayoutable>
@end
@interface ASLayoutSpec()
{
ASLayoutOptions *_layoutOptions;
ASDN::RecursiveMutex _layoutOptionsLock;
}
@end

View File

@@ -10,25 +10,16 @@
#import "ASLayout.h"
#import "ASDimension.h"
#import "ASBaselineLayoutSpec.h"
#import "ASStackPositionedLayout.h"
typedef struct {
/** Describes how the stack will be laid out */
ASStackLayoutSpecStyle stackLayoutStyle;
/** The type of baseline alignment */
ASBaselineLayoutBaselineAlignment baselineAlignment;
} ASBaselineLayoutSpecStyle;
struct ASBaselinePositionedLayout {
struct ASStackBaselinePositionedLayout {
const std::vector<ASLayout *> sublayouts;
const CGFloat crossSize;
const CGFloat ascender;
const CGFloat descender;
/** Given a positioned layout, computes each child position using baseline alignment. */
static ASBaselinePositionedLayout compute(const ASStackPositionedLayout &positionedLayout,
const ASBaselineLayoutSpecStyle &style,
const ASSizeRange &constrainedSize);
static ASStackBaselinePositionedLayout compute(const ASStackPositionedLayout &positionedLayout,
const ASStackLayoutSpecStyle &style,
const ASSizeRange &constrainedSize);
};

View File

@@ -8,39 +8,41 @@
*
*/
#import "ASBaselinePositionedLayout.h"
#import "ASStackBaselinePositionedLayout.h"
#import "ASLayoutSpecUtilities.h"
#import "ASStackLayoutSpecUtilities.h"
#import "ASLayoutOptions.h"
static CGFloat baselineForItem(const ASBaselineLayoutSpecStyle &style,
static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style,
const ASLayout *layout) {
__weak id<ASBaselineLayoutable> child = (id<ASBaselineLayoutable>) layout.layoutableObject;
switch (style.baselineAlignment) {
case ASBaselineLayoutBaselineAlignmentNone:
return 0;
case ASBaselineLayoutBaselineAlignmentFirst:
__weak id<ASLayoutable> child = layout.layoutableObject;
switch (style.alignItems) {
case ASStackLayoutAlignItemsBaselineFirst:
return child.ascender;
case ASBaselineLayoutBaselineAlignmentLast:
case ASStackLayoutAlignItemsBaselineLast:
return layout.size.height + child.descender;
default:
return 0;
}
}
static CGFloat baselineOffset(const ASBaselineLayoutSpecStyle &style,
static CGFloat baselineOffset(const ASStackLayoutSpecStyle &style,
const ASLayout *l,
const CGFloat maxAscender,
const CGFloat maxBaseline)
{
if (style.stackLayoutStyle.direction == ASStackLayoutDirectionHorizontal) {
__weak id<ASBaselineLayoutable> child = (id<ASBaselineLayoutable>)l.layoutableObject;
switch (style.baselineAlignment) {
case ASBaselineLayoutBaselineAlignmentFirst:
if (style.direction == ASStackLayoutDirectionHorizontal) {
__weak id<ASLayoutable> child = l.layoutableObject;
switch (style.alignItems) {
case ASStackLayoutAlignItemsBaselineFirst:
return maxAscender - child.ascender;
case ASBaselineLayoutBaselineAlignmentLast:
case ASStackLayoutAlignItemsBaselineLast:
return maxBaseline - baselineForItem(style, l);
case ASBaselineLayoutBaselineAlignmentNone:
default:
return 0;
}
}
@@ -55,12 +57,10 @@ static CGFloat maxDimensionForLayout(const ASLayout *l,
return maxDimension;
}
ASBaselinePositionedLayout ASBaselinePositionedLayout::compute(const ASStackPositionedLayout &positionedLayout,
const ASBaselineLayoutSpecStyle &style,
const ASSizeRange &constrainedSize)
ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const ASStackPositionedLayout &positionedLayout,
const ASStackLayoutSpecStyle &style,
const ASSizeRange &constrainedSize)
{
ASStackLayoutSpecStyle stackStyle = style.stackLayoutStyle;
/* Step 1: Look at each child and determine the distance from the top of the child node it's baseline.
For example, let's say we have the following two text nodes and want to align them to the first baseline:
@@ -91,9 +91,9 @@ ASBaselinePositionedLayout ASBaselinePositionedLayout::compute(const ASStackPosi
our layoutSpec to have it so that it can be baseline aligned with another text node or baseline layout spec.
*/
const auto ascenderIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){
return ((id<ASBaselineLayoutable>)a.layoutableObject).ascender < ((id<ASBaselineLayoutable>)b.layoutableObject).ascender;
return a.layoutableObject.ascender < b.layoutableObject.ascender;
});
const CGFloat maxAscender = baselineIt == positionedLayout.sublayouts.end() ? 0 : ((id<ASBaselineLayoutable>)(*ascenderIt).layoutableObject).ascender;
const CGFloat maxAscender = baselineIt == positionedLayout.sublayouts.end() ? 0 : (*ascenderIt).layoutableObject.ascender;
/*
Step 3: Take each child and update its layout position based on the baseline offset.
@@ -106,14 +106,14 @@ ASBaselinePositionedLayout ASBaselinePositionedLayout::compute(const ASStackPosi
CGPoint p = CGPointZero;
BOOL first = YES;
auto stackedChildren = AS::map(positionedLayout.sublayouts, [&](ASLayout *l) -> ASLayout *{
__weak id<ASBaselineLayoutable> child = (id<ASBaselineLayoutable>) l.layoutableObject;
p = p + directionPoint(stackStyle.direction, child.spacingBefore, 0);
__weak id<ASLayoutable> child = l.layoutableObject;
p = p + directionPoint(style.direction, child.spacingBefore, 0);
if (first) {
// if this is the first item use the previously computed start point
p = l.position;
} else {
// otherwise add the stack spacing
p = p + directionPoint(stackStyle.direction, stackStyle.spacing, 0);
p = p + directionPoint(style.direction, style.spacing, 0);
}
first = NO;
@@ -123,10 +123,10 @@ ASBaselinePositionedLayout ASBaselinePositionedLayout::compute(const ASStackPosi
// If we are a vertical stack, add the item's descender (it is negative) to the offset for the next node. This will ensure we are spacing
// node from baselines and not bounding boxes.
CGFloat spacingAfterBaseline = 0;
if (stackStyle.direction == ASStackLayoutDirectionVertical) {
if (style.direction == ASStackLayoutDirectionVertical) {
spacingAfterBaseline = child.descender;
}
p = p + directionPoint(stackStyle.direction, stackDimension(stackStyle.direction, l.size) + child.spacingAfter + spacingAfterBaseline, 0);
p = p + directionPoint(style.direction, stackDimension(style.direction, l.size) + child.spacingAfter + spacingAfterBaseline, 0);
return l;
});
@@ -142,21 +142,21 @@ ASBaselinePositionedLayout ASBaselinePositionedLayout::compute(const ASStackPosi
*/
const auto it = std::max_element(stackedChildren.begin(), stackedChildren.end(),
[&](ASLayout *a, ASLayout *b) {
return maxDimensionForLayout(a, stackStyle) < maxDimensionForLayout(b, stackStyle);
return maxDimensionForLayout(a, style) < maxDimensionForLayout(b, style);
});
const auto largestChildCrossSize = it == stackedChildren.end() ? 0 : maxDimensionForLayout(*it, stackStyle);
const auto minCrossSize = crossDimension(stackStyle.direction, constrainedSize.min);
const auto maxCrossSize = crossDimension(stackStyle.direction, constrainedSize.max);
const auto largestChildCrossSize = it == stackedChildren.end() ? 0 : maxDimensionForLayout(*it, style);
const auto minCrossSize = crossDimension(style.direction, constrainedSize.min);
const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max);
const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize);
/*
Step 5: finally, we must find the smallest descender (descender is negative). This is since ASBaselineLayoutSpec implements
ASBaselineLayoutable and needs an ascender and descender to lay itself out properly.
ASLayoutable and needs an ascender and descender to lay itself out properly.
*/
const auto descenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASLayout *a, const ASLayout *b){
return a.position.y + a.size.height < b.position.y + b.size.height;
});
const CGFloat minDescender = descenderIt == stackedChildren.end() ? 0 : ((id<ASBaselineLayoutable>)(*descenderIt).layoutableObject).descender;
const CGFloat minDescender = descenderIt == stackedChildren.end() ? 0 : (*descenderIt).layoutableObject.descender;
return {stackedChildren, crossSize, maxAscender, minDescender};
}

View File

@@ -15,6 +15,7 @@ typedef struct {
CGFloat spacing;
ASStackLayoutJustifyContent justifyContent;
ASStackLayoutAlignItems alignItems;
BOOL baselineRelativeArrangement;
} ASStackLayoutSpecStyle;
inline CGFloat stackDimension(const ASStackLayoutDirection direction, const CGSize size)
@@ -62,6 +63,10 @@ inline ASStackLayoutAlignItems alignment(ASStackLayoutAlignSelf childAlignment,
return ASStackLayoutAlignItemsStart;
case ASStackLayoutAlignSelfStretch:
return ASStackLayoutAlignItemsStretch;
case ASStackLayoutAlignSelfBaselineFirst:
return ASStackLayoutAlignItemsBaselineFirst;
case ASStackLayoutAlignSelfBaselineLast:
return ASStackLayoutAlignItemsBaselineLast;
case ASStackLayoutAlignSelfAuto:
default:
return stackAlignment;

View File

@@ -14,6 +14,7 @@
#import "ASLayoutSpecUtilities.h"
#import "ASStackLayoutSpecUtilities.h"
#import "ASLayoutable.h"
#import "ASLayoutOptions.h"
static CGFloat crossOffset(const ASStackLayoutSpecStyle &style,
const ASStackUnpositionedItem &l,
@@ -24,6 +25,8 @@ static CGFloat crossOffset(const ASStackLayoutSpecStyle &style,
return crossSize - crossDimension(style.direction, l.layout.size);
case ASStackLayoutAlignItemsCenter:
return ASFloorPixelValue((crossSize - crossDimension(style.direction, l.layout.size)) / 2);
case ASStackLayoutAlignItemsBaselineFirst:
case ASStackLayoutAlignItemsBaselineLast:
case ASStackLayoutAlignItemsStart:
case ASStackLayoutAlignItemsStretch:
return 0;

View File

@@ -16,7 +16,7 @@
struct ASStackUnpositionedItem {
/** The original source child. */
id<ASStackLayoutable> child;
id<ASLayoutable> child;
/** The proposed layout. */
ASLayout *layout;
};
@@ -31,7 +31,7 @@ struct ASStackUnpositionedLayout {
const CGFloat violation;
/** Given a set of children, computes the unpositioned layouts for those children. */
static ASStackUnpositionedLayout compute(const std::vector<id<ASStackLayoutable>> &children,
static ASStackUnpositionedLayout compute(const std::vector<id<ASLayoutable>> &children,
const ASStackLayoutSpecStyle &style,
const ASSizeRange &sizeRange);
};

View File

@@ -14,11 +14,12 @@
#import "ASLayoutSpecUtilities.h"
#import "ASStackLayoutSpecUtilities.h"
#import "ASLayoutOptions.h"
/**
Sizes the child given the parameters specified, and returns the computed layout.
*/
static ASLayout *crossChildLayout(const id<ASStackLayoutable> child,
static ASLayout *crossChildLayout(const id<ASLayoutable> child,
const ASStackLayoutSpecStyle style,
const CGFloat stackMin,
const CGFloat stackMax,
@@ -186,7 +187,7 @@ static std::function<BOOL(const ASStackUnpositionedItem &)> isFlexibleInViolatio
}
}
ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id<ASStackLayoutable> child)
ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id<ASLayoutable> child)
{
return child.flexGrow && child.flexShrink;
}
@@ -195,7 +196,7 @@ ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id<ASStackLayoutable> child
If we have a single flexible (both shrinkable and growable) child, and our allowed size range is set to a specific
number then we may avoid the first "intrinsic" size calculation.
*/
ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector<id<ASStackLayoutable>> &children,
ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector<id<ASLayoutable>> &children,
const ASStackLayoutSpecStyle &style,
const ASSizeRange &sizeRange)
{
@@ -283,7 +284,7 @@ static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem>
Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and
stretched.
*/
static std::vector<ASStackUnpositionedItem> layoutChildrenAlongUnconstrainedStackDimension(const std::vector<id<ASStackLayoutable>> &children,
static std::vector<ASStackUnpositionedItem> layoutChildrenAlongUnconstrainedStackDimension(const std::vector<id<ASLayoutable>> &children,
const ASStackLayoutSpecStyle &style,
const ASSizeRange &sizeRange,
const CGSize size,
@@ -292,7 +293,7 @@ static std::vector<ASStackUnpositionedItem> layoutChildrenAlongUnconstrainedStac
const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min);
const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max);
return AS::map(children, [&](id<ASStackLayoutable> child) -> ASStackUnpositionedItem {
return AS::map(children, [&](id<ASLayoutable> child) -> ASStackUnpositionedItem {
const BOOL isUnconstrainedFlexBasis = ASRelativeDimensionEqualToRelativeDimension(ASRelativeDimensionUnconstrained, child.flexBasis);
const CGFloat exactStackDimension = ASRelativeDimensionResolve(child.flexBasis, stackDimension(style.direction, size));
@@ -312,7 +313,7 @@ static std::vector<ASStackUnpositionedItem> layoutChildrenAlongUnconstrainedStac
});
}
ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector<id<ASStackLayoutable>> &children,
ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector<id<ASLayoutable>> &children,
const ASStackLayoutSpecStyle &style,
const ASSizeRange &sizeRange)
{

View File

@@ -13,6 +13,7 @@
#import "ASBackgroundLayoutSpec.h"
#import "ASCenterLayoutSpec.h"
#import "ASStackLayoutSpec.h"
#import "ASLayoutOptions.h"
static const ASSizeRange kSize = {{100, 120}, {320, 160}};

View File

@@ -15,6 +15,7 @@
#import "ASBackgroundLayoutSpec.h"
#import "ASRatioLayoutSpec.h"
#import "ASInsetLayoutSpec.h"
#import "ASLayoutOptions.h"
@interface ASStackLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
@end

View File

@@ -9,6 +9,7 @@
#import <XCTest/XCTest.h>
#import "ASTableView.h"
#import "ASDisplayNode+Subclasses.h"
#define NumberOfSections 10
#define NumberOfRowsPerSection 20
@@ -56,8 +57,24 @@
@end
@interface ASTableViewFilledDataSource : NSObject <ASTableViewDataSource, ASTableViewDelegate>
@interface ASTestTextCellNode : ASTextCellNode
/** Calculated by counting how many times -layoutSpecThatFits: is called on the main thread. */
@property (atomic) int numberOfLayoutsOnMainThread;
@end
@implementation ASTestTextCellNode
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
if ([NSThread isMainThread]) {
_numberOfLayoutsOnMainThread++;
}
return [super layoutSpecThatFits:constrainedSize];
}
@end
@interface ASTableViewFilledDataSource : NSObject <ASTableViewDataSource, ASTableViewDelegate>
@end
@implementation ASTableViewFilledDataSource
@@ -74,7 +91,7 @@
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
{
ASTextCellNode *textCellNode = [ASTextCellNode new];
ASTestTextCellNode *textCellNode = [ASTestTextCellNode new];
textCellNode.text = indexPath.description;
return textCellNode;
@@ -202,34 +219,69 @@
}
}
- (void)testRelayoutAllRows
- (void)testRelayoutAllRowsWithNonZeroSizeInitially
{
// Initial width of the table view is 0 and all nodes are measured with this size.
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, 0, 500)
// 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.
CGSize tableViewFinalSize = CGSizeMake(100, 500);
// Width and height are swapped so that a later size change will simulate a rotation
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width)
style:UITableViewStylePlain
asyncDataFetching:YES];
CGSize tableViewFinalSize = CGSizeMake(100, 500);
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
tableView.asyncDelegate = dataSource;
tableView.asyncDataSource = dataSource;
// Trigger layout measurement on all nodes
[tableView reloadData];
[self triggerSizeChangeAndAssertRelayoutAllRowsForTableView:tableView newSize:tableViewFinalSize];
}
- (void)testRelayoutAllRowsWithZeroSizeInitially
{
// 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.
CGSize tableViewFinalSize = CGSizeMake(100, 500);
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectZero
style:UITableViewStylePlain
asyncDataFetching:YES];
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
tableView.asyncDelegate = dataSource;
tableView.asyncDataSource = dataSource;
// Initial configuration
UIView *superview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
[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];
tableView.frame = CGRectMake(0, 0, tableViewFinalSize.width, tableViewFinalSize.height);
CGRect frame = tableView.frame;
frame.size = newSize;
tableView.frame = frame;
[tableView layoutIfNeeded];
XCTestExpectation *nodesMeasuredUsingNewConstrainedSizeExpectation = [self expectationWithDescription:@"nodesMeasuredUsingNewConstrainedSize"];
[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];
ASCellNode *node = [tableView nodeForRowAtIndexPath:indexPath];
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, tableViewFinalSize.width);
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath];
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 1);
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, newSize.width);
}
}
[nodesMeasuredUsingNewConstrainedSizeExpectation fulfill];
@@ -242,4 +294,138 @@
}];
}
- (void)testRelayoutVisibleRowsWhenEditingModeIsChanged
{
CGSize tableViewSize = CGSizeMake(100, 500);
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
style:UITableViewStylePlain
asyncDataFetching:YES];
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
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);
}
}];
NSArray *visibleNodes = [tableView visibleNodes];
XCTAssertGreaterThan(visibleNodes.count, 0);
// Cause table view to enter editing mode.
// Visibile nodes should be re-measured on main thread with the new (smaller) content view width.
// Other nodes are untouched.
XCTestExpectation *relayoutAfterEnablingEditingExpectation = [self expectationWithDescription:@"relayoutAfterEnablingEditing"];
[tableView beginUpdates];
[tableView setEditing:YES];
[tableView endUpdatesAnimated:YES 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];
if ([visibleNodes containsObject:node]) {
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 1);
XCTAssertLessThan(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width);
} else {
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 0);
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width);
}
}
}
[relayoutAfterEnablingEditingExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
if (error) {
XCTFail(@"Expectation failed: %@", error);
}
}];
// Cause table view to leave editing mode.
// Visibile nodes should be re-measured again.
// All nodes should have max constrained width equals to the table view width.
XCTestExpectation *relayoutAfterDisablingEditingExpectation = [self expectationWithDescription:@"relayoutAfterDisablingEditing"];
[tableView beginUpdates];
[tableView setEditing:NO];
[tableView endUpdatesAnimated:YES 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];
BOOL visible = [visibleNodes containsObject:node];
XCTAssertEqual(node.numberOfLayoutsOnMainThread, visible ? 2: 0);
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width);
}
}
[relayoutAfterDisablingEditingExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
if (error) {
XCTFail(@"Expectation failed: %@", error);
}
}];
}
- (void)testRelayoutRowsAfterEditingModeIsChangedAndTheyBecomeVisible
{
CGSize tableViewSize = CGSizeMake(100, 500);
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
style:UITableViewStylePlain
asyncDataFetching:YES];
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
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);
}
}];
// 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.
NSIndexPath *lastRowIndexPath = [NSIndexPath indexPathForRow:(NumberOfRowsPerSection - 1) inSection:(NumberOfSections - 1)];
XCTestExpectation *relayoutExpectation = [self expectationWithDescription:@"relayout"];
[tableView beginUpdates];
[tableView setEditing:YES];
[tableView setContentOffset:CGPointMake(0, CGFLOAT_MAX) animated:YES];
[tableView endUpdatesAnimated:YES completion:^(BOOL completed) {
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:lastRowIndexPath];
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 1);
XCTAssertLessThan(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width);
[relayoutExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
if (error) {
XCTFail(@"Expectation failed: %@", error);
}
}];
}
@end

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

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

View File

@@ -0,0 +1,358 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
05561CFD19D4F94A00CBA93C /* HorizontalScrollCellNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05561CFC19D4F94A00CBA93C /* HorizontalScrollCellNode.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 */; };
18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.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 */
05561CFB19D4F94A00CBA93C /* HorizontalScrollCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HorizontalScrollCellNode.h; sourceTree = "<group>"; };
05561CFC19D4F94A00CBA93C /* HorizontalScrollCellNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = HorizontalScrollCellNode.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>"; };
18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RandomCoreGraphicsNode.h; sourceTree = "<group>"; };
18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RandomCoreGraphicsNode.m; 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 */,
);
sourceTree = "<group>";
};
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 /* HorizontalScrollCellNode.h */,
05561CFC19D4F94A00CBA93C /* HorizontalScrollCellNode.mm */,
18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */,
18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.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 */,
);
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 */
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 = (
18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */,
05561CFD19D4F94A00CBA93C /* HorizontalScrollCellNode.mm in Sources */,
05E2128D19D4DB510098F589 /* ViewController.m in Sources */,
05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */,
05E2128719D4DB510098F589 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
05E212A219D4DB510098F589 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
05E212A319D4DB510098F589 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
05E212A519D4DB510098F589 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
05E212A619D4DB510098F589 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
05E212A219D4DB510098F589 /* Debug */,
05E212A319D4DB510098F589 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
05E212A519D4DB510098F589 /* Debug */,
05E212A619D4DB510098F589 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 05E2127919D4DB510098F589 /* Project object */;
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#define UseAutomaticLayout 1
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

View File

@@ -0,0 +1,27 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import "AppDelegate.h"
#import "ViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]];
[self.window makeKeyAndVisible];
return YES;
}
@end

View File

@@ -0,0 +1,22 @@
/* 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>
/**
* This ASCellNode contains an ASCollectionNode. It intelligently interacts with a containing ASCollectionView or ASTableView,
* to preload and clean up contents as the user scrolls around both vertically and horizontally — in a way that minimizes memory usage.
*/
@interface HorizontalScrollCellNode : ASCellNode <ASCollectionViewDelegate, ASCollectionViewDataSource>
- (instancetype)initWithElementSize:(CGSize)size;
@end

View File

@@ -0,0 +1,100 @@
/* 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 "HorizontalScrollCellNode.h"
#import "RandomCoreGraphicsNode.h"
#import "AppDelegate.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
static const CGFloat kOuterPadding = 16.0f;
static const CGFloat kInnerPadding = 10.0f;
@interface HorizontalScrollCellNode ()
{
ASCollectionNode *_collectionNode;
CGSize _elementSize;
ASDisplayNode *_divider;
}
@end
@implementation HorizontalScrollCellNode
- (instancetype)initWithElementSize:(CGSize)size
{
if (!(self = [super init]))
return nil;
_elementSize = size;
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
flowLayout.itemSize = _elementSize;
flowLayout.minimumInteritemSpacing = kInnerPadding;
_collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:flowLayout];
[self addSubnode:_collectionNode];
// hairline cell separator
_divider = [[ASDisplayNode alloc] init];
_divider.backgroundColor = [UIColor lightGrayColor];
[self addSubnode:_divider];
return self;
}
- (void)didLoad
{
[super didLoad];
_collectionNode.view.asyncDelegate = self;
_collectionNode.view.asyncDataSource = self;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 5;
}
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
{
RandomCoreGraphicsNode *elementNode = [[RandomCoreGraphicsNode alloc] init];
elementNode.preferredFrameSize = _elementSize;
return elementNode;
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
_collectionNode.preferredFrameSize = CGSizeMake(self.bounds.size.width, _elementSize.height);
ASInsetLayoutSpec *insetSpec = [[ASInsetLayoutSpec alloc] init];
insetSpec.insets = UIEdgeInsetsMake(kOuterPadding, 0.0, kOuterPadding, 0.0);
insetSpec.child = _collectionNode;
return insetSpec;
}
// With box model, you don't need to override this method, unless you want to add custom logic.
- (void)layout
{
[super layout];
_collectionNode.view.contentInset = UIEdgeInsetsMake(0.0, kOuterPadding, 0.0, kOuterPadding);
// Manually layout the divider.
CGFloat pixelHeight = 1.0f / [[UIScreen mainScreen] scale];
_divider.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, pixelHeight);
}
@end

View File

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

View File

@@ -0,0 +1,13 @@
//
// RandomCoreGraphicsNode.h
// Sample
//
// Created by Scott Goodson on 9/5/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface RandomCoreGraphicsNode : ASCellNode
@end

View File

@@ -0,0 +1,44 @@
//
// RandomCoreGraphicsNode.m
// Sample
//
// Created by Scott Goodson on 9/5/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import "RandomCoreGraphicsNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
@implementation RandomCoreGraphicsNode
+ (UIColor *)randomColor
{
CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0
CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white
CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black
return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1];
}
+ (void)drawRect:(CGRect)bounds withParameters:(id<NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
{
CGFloat locations[3];
NSMutableArray *colors = [NSMutableArray arrayWithCapacity:3];
[colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]];
locations[0] = 0.0;
[colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]];
locations[1] = 1.0;
[colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]];
locations[2] = ( arc4random() % 256 / 256.0 );
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)colors, locations);
CGGradientDrawingOptions drawingOptions;
CGContextDrawLinearGradient(ctx, gradient, CGPointZero, CGPointMake(bounds.size.width, bounds.size.height), drawingOptions);
CGColorSpaceRelease(colorSpace);
}
@end

View File

@@ -0,0 +1,16 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end

View File

@@ -0,0 +1,84 @@
/* 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>
#import <AsyncDisplayKit/ASAssert.h>
#import "ViewController.h"
#import "HorizontalScrollCellNode.h"
@interface ViewController () <ASTableViewDataSource, ASTableViewDelegate>
{
ASTableView *_tableView;
}
@end
@implementation ViewController
#pragma mark -
#pragma mark UIViewController.
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_tableView.asyncDataSource = self;
_tableView.asyncDelegate = self;
self.title = @"Horizontal Scrolling Gradients";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRedo
target:self
action:@selector(reloadEverything)];
return self;
}
- (void)reloadEverything
{
[_tableView reloadData];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:_tableView];
}
- (void)viewWillLayoutSubviews
{
_tableView.frame = self.view.bounds;
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
#pragma mark -
#pragma mark ASTableView.
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
{
HorizontalScrollCellNode *node = [[HorizontalScrollCellNode alloc] initWithElementSize:CGSizeMake(100, 100)];
return node;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 100;
}
@end

View File

@@ -0,0 +1,20 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

@@ -58,7 +58,9 @@
1A943BF0259746F18D6E423F /* Frameworks */,
1AE410B73DA5C3BD087ACDD7 /* Pods */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
};
05E2128219D4DB510098F589 /* Products */ = {
isa = PBXGroup;

View File

@@ -140,7 +140,7 @@ static const CGFloat kInnerPadding = 10.0f;
ASStackLayoutSpec *stackSpec = [[ASStackLayoutSpec alloc] init];
stackSpec.direction = ASStackLayoutDirectionHorizontal;
stackSpec.spacing = kInnerPadding;
[stackSpec addChildren:!_swappedTextAndImage ? @[_imageNode, _textNode] : @[_textNode, _imageNode]];
[stackSpec setChildren:!_swappedTextAndImage ? @[_imageNode, _textNode] : @[_textNode, _imageNode]];
ASInsetLayoutSpec *insetSpec = [[ASInsetLayoutSpec alloc] init];
insetSpec.insets = UIEdgeInsetsMake(kOuterPadding, kOuterPadding, kOuterPadding, kOuterPadding);