diff --git a/AsyncDisplayKit-iOS/AsyncDisplayKit-iOS.h b/AsyncDisplayKit-iOS/AsyncDisplayKit-iOS.h new file mode 100644 index 0000000000..130cb0da8a --- /dev/null +++ b/AsyncDisplayKit-iOS/AsyncDisplayKit-iOS.h @@ -0,0 +1,19 @@ +/* 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 + +//! Project version number for AsyncDisplayKit-iOS. +FOUNDATION_EXPORT double AsyncDisplayKit_iOSVersionNumber; + +//! Project version string for AsyncDisplayKit-iOS. +FOUNDATION_EXPORT const unsigned char AsyncDisplayKit_iOSVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/AsyncDisplayKit-iOS/Info.plist b/AsyncDisplayKit-iOS/Info.plist new file mode 100644 index 0000000000..63f8161e4b --- /dev/null +++ b/AsyncDisplayKit-iOS/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.facebook.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index bf7e8d2312..a5068cf20d 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -1,17 +1,18 @@ Pod::Spec.new do |spec| spec.name = 'AsyncDisplayKit' - spec.version = '1.1.1' + spec.version = '1.2.2' spec.license = { :type => 'BSD' } spec.homepage = 'http://asyncdisplaykit.org' - spec.authors = { 'Ryan Nystrom' => 'rnystrom@fb.com', 'Scott Goodson' => 'scottg@fb.com' } + spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' } spec.summary = 'Smooth asynchronous user interfaces for iOS apps.' - spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.1.1' } + spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.2.2' } spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/' spec.public_header_files = [ 'AsyncDisplayKit/*.h', 'AsyncDisplayKit/Details/**/*.h', + 'AsyncDisplayKit/Layout/*.h', 'Base/*.h' ] @@ -25,7 +26,10 @@ Pod::Spec.new do |spec| # ASDealloc2MainObject must be compiled with MRR spec.requires_arc = true - spec.exclude_files = ['AsyncDisplayKit/Details/ASDealloc2MainObject.m'] + spec.exclude_files = [ + 'AsyncDisplayKit/Details/ASDealloc2MainObject.h', + 'AsyncDisplayKit/Details/ASDealloc2MainObject.m', + ] spec.subspec 'ASDealloc2MainObject' do |mrr| mrr.requires_arc = false mrr.source_files = [ diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index f78de8cf92..c0bd2307e6 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -26,7 +26,6 @@ 055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */; }; 055F1A3C19ABD43F004DAFF1 /* ASCellNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 055F1A3D19ABD43F004DAFF1 /* ASCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3B19ABD43F004DAFF1 /* ASCellNode.m */; }; 056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */; }; 0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; 057D02C41AC0A66700C7AC3C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C31AC0A66700C7AC3C /* main.m */; }; @@ -141,6 +140,17 @@ 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, ); }; }; 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, ); }; }; + 205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */; }; + 205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; + 205F0E191B37339C007741D0 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 205F0E1A1B37339C007741D0 /* ASAbstractLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */; }; + 205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */; }; + 205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; }; + 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; + 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; }; 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; }; 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; 292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -157,6 +167,10 @@ 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; }; 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */; }; 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; }; + 430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; }; + 430E7C911B4C23F100697A4C /* ASIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */; }; + 430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */; }; 464052201A3F83C40061C0BA /* ASDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 464052211A3F83C40061C0BA /* ASDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521A1A3F83C40061C0BA /* ASDataController.mm */; }; 464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -164,10 +178,169 @@ 464052241A3F83C40061C0BA /* ASLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521D1A3F83C40061C0BA /* ASLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 464052251A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521E1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h */; }; 464052261A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521F1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm */; }; + 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; + 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */; }; + 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */; }; + 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, ); }; }; + 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */; }; + AC21EC101B3D0BF600C8B19A /* ASStackLayoutChild.h in Headers */ = {isa = PBXBuildFile; fileRef = AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutChild.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A501A1139C100143C57 /* ASCollectionView.mm */; }; AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AC47D9421B3B891B00AAEE9D /* ASCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = AC6456071B0A335000CF11B8 /* ASCellNode.m */; }; + AC47D9451B3BB41900AAEE9D /* ASStaticLayoutSpecDimension.h in Headers */ = {isa = PBXBuildFile; fileRef = AC47D9431B3BB41900AAEE9D /* ASStaticLayoutSpecDimension.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AC47D9461B3BB41900AAEE9D /* ASStaticLayoutSpecDimension.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC47D9441B3BB41900AAEE9D /* ASStaticLayoutSpecDimension.mm */; }; + AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = AC6456071B0A335000CF11B8 /* ASCellNode.m */; }; + ACF6ED1A1B17843500DA7C62 /* ASBackgroundLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED1B1B17843500DA7C62 /* ASBackgroundLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */; }; + ACF6ED1C1B17843500DA7C62 /* ASCenterLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */; }; + ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED071B17843500DA7C62 /* ASDimension.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED211B17843500DA7C62 /* ASDimension.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED081B17843500DA7C62 /* ASDimension.mm */; }; + ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED231B17843500DA7C62 /* ASInsetLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */; }; + ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED0B1B17843500DA7C62 /* ASLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED0C1B17843500DA7C62 /* ASLayout.mm */; }; + ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED0D1B17843500DA7C62 /* ASLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED0E1B17843500DA7C62 /* ASLayoutSpec.mm */; }; + ACF6ED2A1B17843500DA7C62 /* ASLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED111B17843500DA7C62 /* ASLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED2B1B17843500DA7C62 /* ASOverlayLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED121B17843500DA7C62 /* ASOverlayLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED131B17843500DA7C62 /* ASOverlayLayoutSpec.mm */; }; + ACF6ED2D1B17843500DA7C62 /* ASRatioLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED141B17843500DA7C62 /* ASRatioLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED151B17843500DA7C62 /* ASRatioLayoutSpec.mm */; }; + ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */; }; + ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED181B17843500DA7C62 /* ASStaticLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED191B17843500DA7C62 /* ASStaticLayoutSpec.mm */; }; + ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; + ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.mm */; }; + ACF6ED4D1B17847A00DA7C62 /* ASLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; }; + ACF6ED4E1B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; }; + ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */; settings = {ATTRIBUTES = (Private, ); }; }; + ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */; }; + ACF6ED511B17847A00DA7C62 /* ASStackUnpositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */; }; + ACF6ED5C1B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */; }; + ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED541B178DC700DA7C62 /* ASDimensionTests.mm */; }; + ACF6ED5E1B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */; }; + ACF6ED601B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */; }; + ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */; }; + ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */; }; + ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */; }; + B31A241E1B0114FD0016AE7A /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35061DF1B010EDF0018CF92 /* AsyncDisplayKit-iOS.h in Headers */ = {isa = PBXBuildFile; fileRef = B35061DE1B010EDF0018CF92 /* AsyncDisplayKit-iOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A501A1139C100143C57 /* ASCollectionView.mm */; }; + B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35061F81B010EFD0018CF92 /* ASControlNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09D5195D050800B7D73C /* ASControlNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35061F91B010EFD0018CF92 /* ASControlNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09D6195D050800B7D73C /* ASControlNode.m */; }; + B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35061FB1B010EFD0018CF92 /* ASDisplayNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09D8195D050800B7D73C /* ASDisplayNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09D9195D050800B7D73C /* ASDisplayNode.mm */; }; + B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35061FE1B010EFD0018CF92 /* ASDisplayNodeExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35061FF1B010EFD0018CF92 /* ASDisplayNodeExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */; }; + B35062001B010EFD0018CF92 /* ASEditableTextNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062011B010EFD0018CF92 /* ASEditableTextNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */; }; + B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09DD195D050800B7D73C /* ASImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09DE195D050800B7D73C /* ASImageNode.mm */; }; + B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */; }; + B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */; }; + B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062091B010EFD0018CF92 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; + B350620A1B010EFD0018CF92 /* ASTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3319ABD3E3004DAFF1 /* ASTableView.mm */; }; + B350620C1B010EFD0018CF92 /* ASTableViewProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350620D1B010EFD0018CF92 /* ASTextNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09DF195D050800B7D73C /* ASTextNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350620E1B010EFD0018CF92 /* ASTextNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E0195D050800B7D73C /* ASTextNode.mm */; }; + B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062101B010EFD0018CF92 /* _ASDisplayLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */; }; + B35062111B010EFD0018CF92 /* _ASDisplayView.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E4195D050800B7D73C /* _ASDisplayView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062121B010EFD0018CF92 /* _ASDisplayView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E5195D050800B7D73C /* _ASDisplayView.mm */; }; + B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */; }; + B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 299DA1A71A828D2900162D41 /* ASBatchContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; }; + B35062171B010EFD0018CF92 /* ASDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521A1A3F83C40061C0BA /* ASDataController.mm */; }; + B35062191B010EFD0018CF92 /* ASDealloc2MainObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350621A1B010EFD0018CF92 /* ASDealloc2MainObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + B350621B1B010EFD0018CF92 /* ASFlowLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */; }; + B350621D1B010EFD0018CF92 /* ASHighlightOverlayLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */; }; + B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521D1A3F83C40061C0BA /* ASLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062211B010EFD0018CF92 /* ASLayoutRangeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062221B010EFD0018CF92 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521E1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h */; }; + B35062231B010EFD0018CF92 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521F1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm */; }; + B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */; }; + B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */; }; + B35062281B010EFD0018CF92 /* ASRangeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599C1A956527007E5DD6 /* ASRangeHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062291B010EFD0018CF92 /* ASRangeHandlerPreload.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350622A1B010EFD0018CF92 /* ASRangeHandlerPreload.mm in Sources */ = {isa = PBXBuildFile; fileRef = 292C599B1A956527007E5DD6 /* ASRangeHandlerPreload.mm */; }; + B350622B1B010EFD0018CF92 /* ASRangeHandlerRender.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350622C1B010EFD0018CF92 /* ASRangeHandlerRender.mm in Sources */ = {isa = PBXBuildFile; fileRef = 292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */; }; + B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350622E1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EA195D050800B7D73C /* ASTextNodeCoreTextAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350622F1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09EB195D050800B7D73C /* ASTextNodeCoreTextAdditions.m */; }; + B35062301B010EFD0018CF92 /* ASTextNodeRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EC195D050800B7D73C /* ASTextNodeRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062311B010EFD0018CF92 /* ASTextNodeRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09ED195D050800B7D73C /* ASTextNodeRenderer.mm */; }; + B35062321B010EFD0018CF92 /* ASTextNodeShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EE195D050800B7D73C /* ASTextNodeShadower.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062331B010EFD0018CF92 /* ASTextNodeShadower.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09EF195D050800B7D73C /* ASTextNodeShadower.m */; }; + B35062341B010EFD0018CF92 /* ASTextNodeTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F0195D050800B7D73C /* ASTextNodeTextKitHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062351B010EFD0018CF92 /* ASTextNodeTextKitHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F1195D050800B7D73C /* ASTextNodeTextKitHelpers.mm */; }; + B35062361B010EFD0018CF92 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F2195D050800B7D73C /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062371B010EFD0018CF92 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F3195D050800B7D73C /* ASTextNodeWordKerner.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062381B010EFD0018CF92 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */; }; + B35062391B010EFD0018CF92 /* ASThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A12195D050800B7D73C /* ASThread.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350623A1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */; }; + B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F9195D050800B7D73C /* _ASAsyncTransaction.m */; }; + B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FB195D050800B7D73C /* _ASAsyncTransactionContainer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */; }; + B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */; }; + B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062441B010EFD0018CF92 /* UIView+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A00195D050800B7D73C /* UIView+ASConvenience.m */; }; + B35062451B010EFD0018CF92 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A2C1A9516B2005ACEAA /* ASBatchFetching.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B35062471B010EFD0018CF92 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 296A0A2D1A9516B2005ACEAA /* ASBatchFetching.m */; }; + B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A02195D050800B7D73C /* _AS-objc-internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */; }; + B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A05195D050800B7D73C /* _ASPendingState.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B350624C1B010EFD0018CF92 /* _ASPendingState.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A06195D050800B7D73C /* _ASPendingState.m */; }; + B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A07195D050800B7D73C /* _ASScopeTimer.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */; }; + B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */; }; + B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */; }; + B35062521B010EFD0018CF92 /* ASDisplayNodeInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */; }; + B35062551B010EFD0018CF92 /* ASSentinel.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A10195D050800B7D73C /* ASSentinel.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B35062561B010EFD0018CF92 /* ASSentinel.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A11195D050800B7D73C /* ASSentinel.m */; }; + B35062571B010F070018CF92 /* ASAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A43195D058D00B7D73C /* ASAssert.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062581B010F070018CF92 /* ASAvailability.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3A1A15563400B4EBED /* ASAvailability.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A44195D058D00B7D73C /* ASBaseDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350625A1B010F070018CF92 /* ASDisplayNodeExtraIvars.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A45195D058D00B7D73C /* ASDisplayNodeExtraIvars.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350625B1B010F070018CF92 /* ASEqualityHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; }; + B350625C1B010F070018CF92 /* ASLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3B1A15563400B4EBED /* ASLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; }; + B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; + B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; @@ -220,7 +393,6 @@ 055F1A3619ABD413004DAFF1 /* ASRangeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeController.h; sourceTree = ""; }; 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeController.mm; sourceTree = ""; }; 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCellNode.h; sourceTree = ""; }; - 055F1A3B19ABD43F004DAFF1 /* ASCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCellNode.m; sourceTree = ""; }; 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSnapshotTestCase.h; sourceTree = ""; }; 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASImageNodeSnapshotTests.m; sourceTree = ""; }; 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASTableViewProtocols.h; sourceTree = ""; }; @@ -230,7 +402,7 @@ 057D02C51AC0A66700C7AC3C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 057D02C61AC0A66700C7AC3C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEditableTextNode.h; sourceTree = ""; }; - 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEditableTextNode.mm; sourceTree = ""; }; + 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASEditableTextNode.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAsyncDisplayKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; 058D09AF195D04C000B7D73C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 058D09B3195D04C000B7D73C /* AsyncDisplayKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit-Prefix.pch"; sourceTree = ""; }; @@ -242,15 +414,15 @@ 058D09D5195D050800B7D73C /* ASControlNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASControlNode.h; sourceTree = ""; }; 058D09D6195D050800B7D73C /* ASControlNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNode.m; sourceTree = ""; }; 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+Subclasses.h"; sourceTree = ""; }; - 058D09D8195D050800B7D73C /* ASDisplayNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNode.h; sourceTree = ""; }; - 058D09D9195D050800B7D73C /* ASDisplayNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNode.mm; sourceTree = ""; }; - 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Subclasses.h"; sourceTree = ""; }; + 058D09D8195D050800B7D73C /* ASDisplayNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDisplayNode.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 058D09D9195D050800B7D73C /* ASDisplayNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDisplayNode.mm; sourceTree = ""; }; + 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "ASDisplayNode+Subclasses.h"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeExtras.h; sourceTree = ""; }; 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeExtras.mm; sourceTree = ""; }; 058D09DD195D050800B7D73C /* ASImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageNode.h; sourceTree = ""; }; - 058D09DE195D050800B7D73C /* ASImageNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASImageNode.mm; sourceTree = ""; }; + 058D09DE195D050800B7D73C /* ASImageNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASImageNode.mm; sourceTree = ""; }; 058D09DF195D050800B7D73C /* ASTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNode.h; sourceTree = ""; }; - 058D09E0195D050800B7D73C /* ASTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNode.mm; sourceTree = ""; }; + 058D09E0195D050800B7D73C /* ASTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTextNode.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayLayer.h; sourceTree = ""; }; 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayLayer.mm; sourceTree = ""; }; 058D09E4195D050800B7D73C /* _ASDisplayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayView.h; sourceTree = ""; }; @@ -316,6 +488,17 @@ 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASSnapshotTestCase.mm; sourceTree = ""; }; 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageProtocols.h; sourceTree = ""; }; 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHelpers.h; sourceTree = ""; }; + 204C979D1B362CB3002B1083 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionViewLayout+ASConvenience.h"; sourceTree = ""; }; + 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionViewLayout+ASConvenience.m"; sourceTree = ""; }; + 205F0E111B371BD7007741D0 /* ASScrollDirection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollDirection.m; sourceTree = ""; }; + 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAbstractLayoutController.h; sourceTree = ""; }; + 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASAbstractLayoutController.mm; sourceTree = ""; }; + 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewLayoutController.h; sourceTree = ""; }; + 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionViewLayoutController.mm; sourceTree = ""; }; + 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = ""; }; + 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = ""; }; + 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; 2911485B1A77147A005D0878 /* ASControlNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNodeTests.m; sourceTree = ""; }; 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutRangeType.h; sourceTree = ""; }; 292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerPreload.h; sourceTree = ""; }; @@ -332,6 +515,8 @@ 299DA1A81A828D2900162D41 /* ASBatchContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBatchContext.mm; sourceTree = ""; }; 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderContextTests.m; sourceTree = ""; }; 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewTests.m; sourceTree = ""; }; + 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexPath.h; sourceTree = ""; }; + 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexPath.m; sourceTree = ""; }; 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDataController.h; sourceTree = ""; }; 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDataController.mm; sourceTree = ""; }; 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = ""; }; @@ -340,9 +525,54 @@ 4640521E1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMultidimensionalArrayUtils.h; sourceTree = ""; }; 4640521F1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMultidimensionalArrayUtils.mm; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; + 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewTests.m; sourceTree = ""; }; + AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutChild.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutChild.h; path = AsyncDisplayKit/Layout/ASStackLayoutChild.h; sourceTree = ""; }; AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionView.h; sourceTree = ""; }; AC3C4A501A1139C100143C57 /* ASCollectionView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionView.mm; sourceTree = ""; }; AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewProtocols.h; sourceTree = ""; }; + AC47D9431B3BB41900AAEE9D /* ASStaticLayoutSpecDimension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStaticLayoutSpecDimension.h; path = AsyncDisplayKit/Layout/ASStaticLayoutSpecDimension.h; sourceTree = ""; }; + AC47D9441B3BB41900AAEE9D /* ASStaticLayoutSpecDimension.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASStaticLayoutSpecDimension.mm; path = AsyncDisplayKit/Layout/ASStaticLayoutSpecDimension.mm; sourceTree = ""; }; + AC6456071B0A335000CF11B8 /* ASCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCellNode.m; sourceTree = ""; }; + ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASBackgroundLayoutSpec.h; path = AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h; sourceTree = ""; }; + ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASBackgroundLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm; sourceTree = ""; }; + ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASCenterLayoutSpec.h; path = AsyncDisplayKit/Layout/ASCenterLayoutSpec.h; sourceTree = ""; }; + ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASCenterLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm; sourceTree = ""; }; + ACF6ED071B17843500DA7C62 /* ASDimension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDimension.h; path = AsyncDisplayKit/Layout/ASDimension.h; sourceTree = ""; }; + ACF6ED081B17843500DA7C62 /* ASDimension.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASDimension.mm; path = AsyncDisplayKit/Layout/ASDimension.mm; sourceTree = ""; }; + ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASInsetLayoutSpec.h; path = AsyncDisplayKit/Layout/ASInsetLayoutSpec.h; sourceTree = ""; }; + ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASInsetLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm; sourceTree = ""; }; + ACF6ED0B1B17843500DA7C62 /* ASLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayout.h; path = AsyncDisplayKit/Layout/ASLayout.h; sourceTree = ""; }; + ACF6ED0C1B17843500DA7C62 /* ASLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASLayout.mm; path = AsyncDisplayKit/Layout/ASLayout.mm; sourceTree = ""; }; + ACF6ED0D1B17843500DA7C62 /* ASLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutSpec.h; path = AsyncDisplayKit/Layout/ASLayoutSpec.h; sourceTree = ""; }; + ACF6ED0E1B17843500DA7C62 /* ASLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASLayoutSpec.mm; sourceTree = ""; }; + ACF6ED111B17843500DA7C62 /* ASLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutable.h; path = AsyncDisplayKit/Layout/ASLayoutable.h; sourceTree = ""; }; + ACF6ED121B17843500DA7C62 /* ASOverlayLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASOverlayLayoutSpec.h; path = AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h; sourceTree = ""; }; + ACF6ED131B17843500DA7C62 /* ASOverlayLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASOverlayLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm; sourceTree = ""; }; + ACF6ED141B17843500DA7C62 /* ASRatioLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRatioLayoutSpec.h; path = AsyncDisplayKit/Layout/ASRatioLayoutSpec.h; sourceTree = ""; }; + ACF6ED151B17843500DA7C62 /* ASRatioLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASRatioLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm; sourceTree = ""; }; + ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutSpec.h; path = AsyncDisplayKit/Layout/ASStackLayoutSpec.h; sourceTree = ""; }; + ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASStackLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASStackLayoutSpec.mm; sourceTree = ""; }; + ACF6ED181B17843500DA7C62 /* ASStaticLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStaticLayoutSpec.h; path = AsyncDisplayKit/Layout/ASStaticLayoutSpec.h; sourceTree = ""; }; + ACF6ED191B17843500DA7C62 /* ASStaticLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASStaticLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASInternalHelpers.h; sourceTree = ""; }; + ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASInternalHelpers.mm; sourceTree = ""; }; + ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecUtilities.h; sourceTree = ""; }; + ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutSpecUtilities.h; sourceTree = ""; }; + ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackPositionedLayout.h; sourceTree = ""; }; + ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackPositionedLayout.mm; sourceTree = ""; }; + ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackUnpositionedLayout.h; sourceTree = ""; }; + ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASStackUnpositionedLayout.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCenterLayoutSpecSnapshotTests.mm; sourceTree = ""; }; + ACF6ED541B178DC700DA7C62 /* ASDimensionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDimensionTests.mm; sourceTree = ""; }; + ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASInsetLayoutSpecSnapshotTests.mm; sourceTree = ""; }; + ACF6ED571B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecSnapshotTestsHelper.h; sourceTree = ""; }; + ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASLayoutSpecSnapshotTestsHelper.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASOverlayLayoutSpecSnapshotTests.mm; sourceTree = ""; }; + ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRatioLayoutSpecSnapshotTests.mm; sourceTree = ""; }; + ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackLayoutSpecSnapshotTests.mm; sourceTree = ""; }; + B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B35061DD1B010EDF0018CF92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B35061DE1B010EDF0018CF92 /* AsyncDisplayKit-iOS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit-iOS.h"; sourceTree = ""; }; D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; @@ -382,12 +612,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B35061D61B010EDF0018CF92 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */, + B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */, + B350625D1B0111740018CF92 /* Photos.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */ = { isa = PBXGroup; children = ( + 204C979D1B362CB3002B1083 /* Default-568h@2x.png */, 057D02C51AC0A66700C7AC3C /* AppDelegate.h */, 057D02C61AC0A66700C7AC3C /* AppDelegate.m */, 057D02C11AC0A66700C7AC3C /* Supporting Files */, @@ -410,6 +651,7 @@ children = ( 058D09B1195D04C000B7D73C /* AsyncDisplayKit */, 058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */, + B35061DB1B010EDF0018CF92 /* AsyncDisplayKit-iOS */, 058D09AE195D04C000B7D73C /* Frameworks */, 058D09AD195D04C000B7D73C /* Products */, FD40E2760492F0CAAEAD552D /* Pods */, @@ -422,6 +664,7 @@ 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */, 058D09BC195D04C000B7D73C /* AsyncDisplayKitTests.xctest */, 057D02BF1AC0A66700C7AC3C /* AsyncDisplayKitTestHost.app */, + B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */, ); name = Products; sourceTree = ""; @@ -443,7 +686,7 @@ isa = PBXGroup; children = ( 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */, - 055F1A3B19ABD43F004DAFF1 /* ASCellNode.m */, + AC6456071B0A335000CF11B8 /* ASCellNode.m */, AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */, AC3C4A501A1139C100143C57 /* ASCollectionView.mm */, AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */, @@ -473,6 +716,7 @@ 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, 058D09E1195D050800B7D73C /* Details */, 058D0A01195D050800B7D73C /* Private */, + AC6456051B0A333200CF11B8 /* Layout */, 058D09B2195D04C000B7D73C /* Supporting Files */, ); path = AsyncDisplayKit; @@ -494,9 +738,19 @@ 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */, 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */, 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */, + ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */, + ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */, + ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */, + ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, + ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */, + ACF6ED571B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.h */, + ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */, + 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */, 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */, 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */, + 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */, 2911485B1A77147A005D0878 /* ASControlNodeTests.m */, + ACF6ED541B178DC700DA7C62 /* ASDimensionTests.mm */, 058D0A2D195D057000B7D73C /* ASDisplayLayerTests.m */, 058D0A2E195D057000B7D73C /* ASDisplayNodeAppearanceTests.m */, 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.m */, @@ -532,10 +786,14 @@ 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */, 058D09E4195D050800B7D73C /* _ASDisplayView.h */, 058D09E5195D050800B7D73C /* _ASDisplayView.mm */, + 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */, + 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */, 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */, 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */, 299DA1A71A828D2900162D41 /* ASBatchContext.h */, 299DA1A81A828D2900162D41 /* ASBatchContext.mm */, + 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */, + 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */, 464052191A3F83C40061C0BA /* ASDataController.h */, 4640521A1A3F83C40061C0BA /* ASDataController.mm */, 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */, @@ -544,6 +802,8 @@ 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */, 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */, 058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */, + 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */, + 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */, 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */, 4640521D1A3F83C40061C0BA /* ASLayoutController.h */, 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */, @@ -559,6 +819,7 @@ 292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */, 292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */, 296A0A311A951715005ACEAA /* ASScrollDirection.h */, + 205F0E111B371BD7007741D0 /* ASScrollDirection.m */, 058D09EA195D050800B7D73C /* ASTextNodeCoreTextAdditions.h */, 058D09EB195D050800B7D73C /* ASTextNodeCoreTextAdditions.m */, 058D09EC195D050800B7D73C /* ASTextNodeRenderer.h */, @@ -571,9 +832,13 @@ 058D09F3195D050800B7D73C /* ASTextNodeWordKerner.h */, 058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */, 058D0A12195D050800B7D73C /* ASThread.h */, + 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */, + 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */, 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */, 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */, 058D09F7195D050800B7D73C /* Transactions */, + 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */, + 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */, 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */, 058D0A00195D050800B7D73C /* UIView+ASConvenience.m */, ); @@ -615,6 +880,14 @@ 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */, 058D0A10195D050800B7D73C /* ASSentinel.h */, 058D0A11195D050800B7D73C /* ASSentinel.m */, + ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */, + ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.mm */, + ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */, + ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */, + ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */, + ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */, + ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */, + ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */, ); path = Private; sourceTree = ""; @@ -632,6 +905,56 @@ path = Base; sourceTree = SOURCE_ROOT; }; + AC6456051B0A333200CF11B8 /* Layout */ = { + isa = PBXGroup; + children = ( + ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */, + ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */, + ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */, + ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */, + ACF6ED071B17843500DA7C62 /* ASDimension.h */, + ACF6ED081B17843500DA7C62 /* ASDimension.mm */, + ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */, + ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */, + ACF6ED0B1B17843500DA7C62 /* ASLayout.h */, + ACF6ED0C1B17843500DA7C62 /* ASLayout.mm */, + ACF6ED111B17843500DA7C62 /* ASLayoutable.h */, + ACF6ED0D1B17843500DA7C62 /* ASLayoutSpec.h */, + ACF6ED0E1B17843500DA7C62 /* ASLayoutSpec.mm */, + ACF6ED121B17843500DA7C62 /* ASOverlayLayoutSpec.h */, + ACF6ED131B17843500DA7C62 /* ASOverlayLayoutSpec.mm */, + ACF6ED141B17843500DA7C62 /* ASRatioLayoutSpec.h */, + ACF6ED151B17843500DA7C62 /* ASRatioLayoutSpec.mm */, + AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutChild.h */, + ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */, + ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */, + ACF6ED181B17843500DA7C62 /* ASStaticLayoutSpec.h */, + ACF6ED191B17843500DA7C62 /* ASStaticLayoutSpec.mm */, + AC47D9431B3BB41900AAEE9D /* ASStaticLayoutSpecDimension.h */, + AC47D9441B3BB41900AAEE9D /* ASStaticLayoutSpecDimension.mm */, + ); + name = Layout; + path = ..; + sourceTree = ""; + }; + B35061DB1B010EDF0018CF92 /* AsyncDisplayKit-iOS */ = { + isa = PBXGroup; + children = ( + B35061DE1B010EDF0018CF92 /* AsyncDisplayKit-iOS.h */, + B35061DC1B010EDF0018CF92 /* Supporting Files */, + ); + name = "AsyncDisplayKit-iOS"; + path = AsyncDisplayKit; + sourceTree = ""; + }; + B35061DC1B010EDF0018CF92 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + B35061DD1B010EDF0018CF92 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; FD40E2760492F0CAAEAD552D /* Pods */ = { isa = PBXGroup; children = ( @@ -648,6 +971,20 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + AC21EC101B3D0BF600C8B19A /* ASStackLayoutChild.h in Headers */, + AC47D9451B3BB41900AAEE9D /* ASStaticLayoutSpecDimension.h in Headers */, + ACF6ED511B17847A00DA7C62 /* ASStackUnpositionedLayout.h in Headers */, + ACF6ED2D1B17843500DA7C62 /* ASRatioLayoutSpec.h in Headers */, + ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */, + ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */, + ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */, + ACF6ED2B1B17843500DA7C62 /* ASOverlayLayoutSpec.h in Headers */, + ACF6ED1C1B17843500DA7C62 /* ASCenterLayoutSpec.h in Headers */, + ACF6ED2A1B17843500DA7C62 /* ASLayoutable.h in Headers */, + ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */, + ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */, + ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */, + ACF6ED1A1B17843500DA7C62 /* ASBackgroundLayoutSpec.h in Headers */, 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */, 464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */, 464052241A3F83C40061C0BA /* ASLayoutController.h in Headers */, @@ -660,6 +997,7 @@ 058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */, 1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */, 058D0A4B195D05CB00B7D73C /* ASDisplayNode.mm in Headers */, + 430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */, 058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */, 058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */, 058D0A4E195D05CB00B7D73C /* ASDisplayNodeExtras.mm in Headers */, @@ -683,6 +1021,7 @@ 058D0A5A195D05DC00B7D73C /* ASMutableAttributedStringBuilder.m in Headers */, 058D0A5B195D05DC00B7D73C /* ASTextNodeCoreTextAdditions.h in Headers */, 058D0A5C195D05DC00B7D73C /* ASTextNodeCoreTextAdditions.m in Headers */, + 205F0E191B37339C007741D0 /* ASAbstractLayoutController.h in Headers */, 058D0A5D195D05DC00B7D73C /* ASTextNodeRenderer.h in Headers */, 058D0A5E195D05DC00B7D73C /* ASTextNodeRenderer.mm in Headers */, 058D0A5F195D05DC00B7D73C /* ASTextNodeShadower.h in Headers */, @@ -698,6 +1037,7 @@ 058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */, 058D0A67195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Headers */, 058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */, + 205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */, 058D0A69195D05EC00B7D73C /* _ASAsyncTransaction.m in Headers */, 058D0A6A195D05EC00B7D73C /* _ASAsyncTransactionContainer+Private.h in Headers */, 058D0A6B195D05EC00B7D73C /* _ASAsyncTransactionContainer.h in Headers */, @@ -705,8 +1045,10 @@ 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */, 058D0A6D195D05EC00B7D73C /* _ASAsyncTransactionGroup.h in Headers */, 058D0A6E195D05EC00B7D73C /* _ASAsyncTransactionGroup.m in Headers */, + 205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */, 058D0A6F195D05EC00B7D73C /* UIView+ASConvenience.h in Headers */, 058D0A70195D05EC00B7D73C /* UIView+ASConvenience.m in Headers */, + ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */, 058D0A82195D060300B7D73C /* ASAssert.h in Headers */, 0516FA3C1A15563400B4EBED /* ASAvailability.h in Headers */, 0516FA3D1A15563400B4EBED /* ASLog.h in Headers */, @@ -715,6 +1057,9 @@ AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */, 292C59A01A956527007E5DD6 /* ASRangeHandlerPreload.h in Headers */, 055B9FA81A1C154B00035D6D /* ASNetworkImageNode.h in Headers */, + ACF6ED4E1B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h in Headers */, + ACF6ED4D1B17847A00DA7C62 /* ASLayoutSpecUtilities.h in Headers */, + ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */, 054963491A1EA066000F8E56 /* ASBasicImageDownloader.h in Headers */, AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */, D785F6621A74327E00291744 /* ASScrollNode.h in Headers */, @@ -731,6 +1076,7 @@ 058D0A79195D05F900B7D73C /* ASDisplayNode+DebugTiming.mm in Headers */, 058D0A7A195D05F900B7D73C /* ASDisplayNode+UIViewBridge.mm in Headers */, 2967F9E21AB0A5190072E4AB /* ASBasicImageDownloaderInternal.h in Headers */, + 205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */, 058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */, 058D0A7C195D05F900B7D73C /* ASImageNode+CGExtras.h in Headers */, 058D0A7D195D05F900B7D73C /* ASImageNode+CGExtras.m in Headers */, @@ -741,6 +1087,82 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B35061D71B010EDF0018CF92 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + B35062321B010EFD0018CF92 /* ASTextNodeShadower.h in Headers */, + B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */, + B31A241E1B0114FD0016AE7A /* AsyncDisplayKit.h in Headers */, + B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */, + B35061FB1B010EFD0018CF92 /* ASDisplayNode.h in Headers */, + B35062361B010EFD0018CF92 /* ASTextNodeTypes.h in Headers */, + B35062341B010EFD0018CF92 /* ASTextNodeTextKitHelpers.h in Headers */, + B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */, + B35062371B010EFD0018CF92 /* ASTextNodeWordKerner.h in Headers */, + B35062261B010EFD0018CF92 /* ASRangeController.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 */, + B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */, + B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */, + B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */, + B35062571B010F070018CF92 /* ASAssert.h in Headers */, + B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */, + B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */, + B350625C1B010F070018CF92 /* ASLog.h in Headers */, + B35062551B010EFD0018CF92 /* ASSentinel.h in Headers */, + B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */, + B35062391B010EFD0018CF92 /* ASThread.h in Headers */, + B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */, + B35062221B010EFD0018CF92 /* ASMultidimensionalArrayUtils.h in Headers */, + B350625B1B010F070018CF92 /* ASEqualityHelpers.h in Headers */, + B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */, + 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */, + B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */, + B350621D1B010EFD0018CF92 /* ASHighlightOverlayLayer.h in Headers */, + B35062171B010EFD0018CF92 /* ASDataController.h in Headers */, + B350625A1B010F070018CF92 /* ASDisplayNodeExtraIvars.h in Headers */, + B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */, + B35061DF1B010EDF0018CF92 /* AsyncDisplayKit-iOS.h in Headers */, + B350620A1B010EFD0018CF92 /* ASTableView.h in Headers */, + B35062451B010EFD0018CF92 /* ASBatchFetching.h in Headers */, + B350620C1B010EFD0018CF92 /* ASTableViewProtocols.h in Headers */, + B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */, + B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */, + B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, + B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */, + B35062581B010F070018CF92 /* ASAvailability.h in Headers */, + B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */, + B350622B1B010EFD0018CF92 /* ASRangeHandlerRender.h in Headers */, + B350622E1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.h in Headers */, + B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */, + B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */, + 509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */, + B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */, + B35062211B010EFD0018CF92 /* ASLayoutRangeType.h in Headers */, + B35062521B010EFD0018CF92 /* ASDisplayNodeInternal.h in Headers */, + B35061FE1B010EFD0018CF92 /* ASDisplayNodeExtras.h in Headers */, + B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */, + B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */, + B35062301B010EFD0018CF92 /* ASTextNodeRenderer.h in Headers */, + 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */, + B350620D1B010EFD0018CF92 /* ASTextNode.h in Headers */, + B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */, + B350621B1B010EFD0018CF92 /* ASFlowLayoutController.h in Headers */, + B35062291B010EFD0018CF92 /* ASRangeHandlerPreload.h in Headers */, + B35062001B010EFD0018CF92 /* ASEditableTextNode.h in Headers */, + B350623A1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.h in Headers */, + B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */, + B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */, + B35062191B010EFD0018CF92 /* ASDealloc2MainObject.h in Headers */, + B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */, + B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -800,6 +1222,24 @@ productReference = 058D09BC195D04C000B7D73C /* AsyncDisplayKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + B35061D91B010EDF0018CF92 /* AsyncDisplayKit-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = B35061ED1B010EDF0018CF92 /* Build configuration list for PBXNativeTarget "AsyncDisplayKit-iOS" */; + buildPhases = ( + B35061D51B010EDF0018CF92 /* Sources */, + B35061D61B010EDF0018CF92 /* Frameworks */, + B35061D71B010EDF0018CF92 /* Headers */, + B35061D81B010EDF0018CF92 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "AsyncDisplayKit-iOS"; + productName = AsyncDisplayKit; + productReference = B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */; + productType = "com.apple.product-type.framework"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -815,6 +1255,9 @@ 058D09BB195D04C000B7D73C = { TestTargetID = 057D02BE1AC0A66700C7AC3C; }; + B35061D91B010EDF0018CF92 = { + CreatedOnToolsVersion = 6.3.1; + }; }; }; buildConfigurationList = 058D09A7195D04C000B7D73C /* Build configuration list for PBXProject "AsyncDisplayKit" */; @@ -833,6 +1276,7 @@ 058D09AB195D04C000B7D73C /* AsyncDisplayKit */, 058D09BB195D04C000B7D73C /* AsyncDisplayKitTests */, 057D02BE1AC0A66700C7AC3C /* AsyncDisplayKitTestHost */, + B35061D91B010EDF0018CF92 /* AsyncDisplayKit-iOS */, ); }; /* End PBXProject section */ @@ -842,6 +1286,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 204C979E1B362CB3002B1083 /* Default-568h@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -854,6 +1299,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B35061D81B010EDF0018CF92 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -903,18 +1355,32 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 430E7C911B4C23F100697A4C /* ASIndexPath.m in Sources */, 058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */, 058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */, 058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */, + ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */, 058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */, + ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */, + ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */, 058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */, + 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */, + 205F0E1A1B37339C007741D0 /* ASAbstractLayoutController.mm in Sources */, 464052211A3F83C40061C0BA /* ASDataController.mm in Sources */, 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */, 058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */, + ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */, 058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */, + ACF6ED1B1B17843500DA7C62 /* ASBackgroundLayoutSpec.mm in Sources */, 055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */, + 205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */, + AC47D9461B3BB41900AAEE9D /* ASStaticLayoutSpecDimension.mm in Sources */, + ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */, + ACF6ED211B17843500DA7C62 /* ASDimension.mm in Sources */, 464052261A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm in Sources */, 055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */, + ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */, + ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */, 058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */, 292C59A41A956527007E5DD6 /* ASRangeHandlerRender.mm in Sources */, 058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */, @@ -926,23 +1392,29 @@ 058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */, 0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm 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 */, 058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */, 058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */, - 055F1A3D19ABD43F004DAFF1 /* ASCellNode.m in Sources */, 058D0A1C195D050800B7D73C /* ASTextNodeCoreTextAdditions.m in Sources */, 058D0A13195D050800B7D73C /* ASControlNode.m in Sources */, + ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.mm in Sources */, 058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */, + 205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */, 05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */, 058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */, + AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */, + ACF6ED231B17843500DA7C62 /* ASInsetLayoutSpec.mm in Sources */, 058D0A27195D050800B7D73C /* _ASPendingState.m in Sources */, 0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */, 058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */, 058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */, + 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */, 058D0A22195D050800B7D73C /* _ASAsyncTransaction.m in Sources */, + ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */, 055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */, 296A0A2F1A9516B2005ACEAA /* ASBatchFetching.m in Sources */, D785F6631A74327E00291744 /* ASScrollNode.m in Sources */, @@ -953,26 +1425,90 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */, + ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */, 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */, 296A0A351A951ABF005ACEAA /* ASBatchFetchingTests.m in Sources */, 058D0A3E195D057000B7D73C /* ASTextNodeRendererTests.m in Sources */, 058D0A3D195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m in Sources */, 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */, + 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */, 058D0A3F195D057000B7D73C /* ASTextNodeShadowerTests.m in Sources */, + ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */, 056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */, 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */, + ACF6ED5E1B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm in Sources */, 058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */, 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */, + ACF6ED601B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m in Sources */, 052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */, + ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */, 058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */, 058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */, + ACF6ED5C1B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm in Sources */, + 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */, 058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */, 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */, 058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + B35061D51B010EDF0018CF92 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, + B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */, + B35062311B010EFD0018CF92 /* ASTextNodeRenderer.mm in Sources */, + B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */, + B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */, + B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, + B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */, + B35062471B010EFD0018CF92 /* ASBatchFetching.m in Sources */, + B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */, + B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, + 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, + B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */, + B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.m in Sources */, + B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */, + B350620E1B010EFD0018CF92 /* ASTextNode.mm in Sources */, + B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, + B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */, + B35062231B010EFD0018CF92 /* ASMultidimensionalArrayUtils.mm in Sources */, + 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */, + B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */, + B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */, + B35061F91B010EFD0018CF92 /* ASControlNode.m in Sources */, + AC47D9421B3B891B00AAEE9D /* ASCellNode.m in Sources */, + 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */, + B35062561B010EFD0018CF92 /* ASSentinel.m in Sources */, + B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */, + B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */, + B35062011B010EFD0018CF92 /* ASEditableTextNode.mm in Sources */, + B35062441B010EFD0018CF92 /* UIView+ASConvenience.m in Sources */, + B350622F1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.m in Sources */, + B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */, + B35062091B010EFD0018CF92 /* ASScrollNode.m in Sources */, + B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */, + 430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */, + B35062381B010EFD0018CF92 /* ASTextNodeWordKerner.m in Sources */, + B35062101B010EFD0018CF92 /* _ASDisplayLayer.mm in Sources */, + B35062351B010EFD0018CF92 /* ASTextNodeTextKitHelpers.mm in Sources */, + B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */, + B35061FF1B010EFD0018CF92 /* ASDisplayNodeExtras.mm in Sources */, + B35062121B010EFD0018CF92 /* _ASDisplayView.mm in Sources */, + B350624C1B010EFD0018CF92 /* _ASPendingState.m in Sources */, + B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */, + 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */, + B350622C1B010EFD0018CF92 /* ASRangeHandlerRender.mm in Sources */, + B350622A1B010EFD0018CF92 /* ASRangeHandlerPreload.mm in Sources */, + B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */, + B350621A1B010EFD0018CF92 /* ASDealloc2MainObject.m in Sources */, + B35062331B010EFD0018CF92 /* ASTextNodeShadower.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -1194,6 +1730,63 @@ }; name = Release; }; + B35061EE1B010EDF0018CF92 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_WARN_UNREACHABLE_CODE = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = AsyncDisplayKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = AsyncDisplayKit; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + B35061EF1B010EDF0018CF92 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_WARN_UNREACHABLE_CODE = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = AsyncDisplayKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = AsyncDisplayKit; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1233,6 +1826,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + B35061ED1B010EDF0018CF92 /* Build configuration list for PBXNativeTarget "AsyncDisplayKit-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B35061EE1B010EDF0018CF92 /* Debug */, + B35061EF1B010EDF0018CF92 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 058D09A4195D04C000B7D73C /* Project object */; diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme new file mode 100644 index 0000000000..2a1226a3f6 --- /dev/null +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 3c7559f85f..39aadab6bb 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -12,6 +12,7 @@ #import #import +#import #pragma mark - #pragma mark ASCellNode @@ -25,6 +26,7 @@ // use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; + self.clipsToBounds = YES; return self; } @@ -91,8 +93,6 @@ @implementation ASTextCellNode -static const CGFloat kHorizontalPadding = 15.0f; -static const CGFloat kVerticalPadding = 11.0f; static const CGFloat kFontSize = 18.0f; - (instancetype)init @@ -106,19 +106,12 @@ static const CGFloat kFontSize = 18.0f; return self; } -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize +- (id)layoutSpecThatFits:(ASSizeRange)constrainedSize { - CGSize availableSize = CGSizeMake(constrainedSize.width - 2 * kHorizontalPadding, - constrainedSize.height - 2 * kVerticalPadding); - CGSize textNodeSize = [_textNode measure:availableSize]; - - return CGSizeMake(ceilf(2 * kHorizontalPadding + textNodeSize.width), - ceilf(2 * kVerticalPadding + textNodeSize.height)); -} - -- (void)layout -{ - _textNode.frame = CGRectInset(self.bounds, kHorizontalPadding, kVerticalPadding); + static const CGFloat kHorizontalPadding = 15.0f; + static const CGFloat kVerticalPadding = 11.0f; + UIEdgeInsets insets = UIEdgeInsetsMake(kVerticalPadding, kHorizontalPadding, kVerticalPadding, kHorizontalPadding); + return [ASInsetLayoutSpec newWithInsets:insets child:_textNode]; } - (void)setText:(NSString *)text @@ -130,7 +123,7 @@ static const CGFloat kFontSize = 18.0f; _textNode.attributedString = [[NSAttributedString alloc] initWithString:_text attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kFontSize]}]; - [self invalidateCalculatedSize]; + [self invalidateCalculatedLayout]; } @end diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index b99f55a7da..ec6fd3242e 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -70,6 +70,15 @@ */ @property (nonatomic, assign) CGFloat leadingScreensForBatching; +/** + * Reload everything from scratch, destroying the working range and all cached nodes. + * + * @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on + * the main thread. + * @warning This method is substantially more expensive than UICollectionView's version. + */ +- (void)reloadDataWithCompletion:(void (^)())completion; + /** * Reload everything from scratch, destroying the working range and all cached nodes. * @@ -122,6 +131,20 @@ */ - (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; +/** + * Determines collection view's current scroll direction. Supports 2-axis collection views. + * + * @returns a bitmask of ASScrollDirection values. + */ +- (ASScrollDirection)scrollDirection; + +/** + * Determines collection view's scrollable directions. + * + * @returns a bitmask of ASScrollDirection values. + */ +- (ASScrollDirection)scrollableDirections; + @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 80786df40d..36a995f90b 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -9,13 +9,14 @@ #import "ASCollectionView.h" #import "ASAssert.h" -#import "ASFlowLayoutController.h" +#import "ASCollectionViewLayoutController.h" #import "ASRangeController.h" #import "ASDataController.h" #import "ASDisplayNodeInternal.h" #import "ASBatchFetching.h" +#import "UICollectionViewLayout+ASConvenience.h" -const static NSUInteger kASCollectionViewAnimationNone = 0; +const static NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone; #pragma mark - @@ -78,11 +79,17 @@ static BOOL _isInterceptedSelector(SEL sel) - (BOOL)respondsToSelector:(SEL)aSelector { + ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early + ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); + return (_isInterceptedSelector(aSelector) || [_target respondsToSelector:aSelector]); } - (id)forwardingTargetForSelector:(SEL)aSelector { + ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early + ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); + if (_isInterceptedSelector(aSelector)) { return _interceptor; } @@ -102,7 +109,7 @@ static BOOL _isInterceptedSelector(SEL sel) ASDataController *_dataController; ASRangeController *_rangeController; - ASFlowLayoutController *_layoutController; + ASCollectionViewLayoutController *_layoutController; BOOL _performingBatchUpdates; NSMutableArray *_batchUpdateBlocks; @@ -130,11 +137,12 @@ static BOOL _isInterceptedSelector(SEL sel) { if (!(self = [super initWithFrame:frame collectionViewLayout:layout])) return nil; + + // FIXME: asyncDataFetching is currently unreliable for some use cases. + // https://github.com/facebook/AsyncDisplayKit/issues/385 + asyncDataFetchingEnabled = NO; - ASDisplayNodeAssert([layout isKindOfClass:UICollectionViewFlowLayout.class], @"only flow layouts are currently supported"); - - ASFlowLayoutDirection direction = (((UICollectionViewFlowLayout *)layout).scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASFlowLayoutDirectionHorizontal : ASFlowLayoutDirectionVertical; - _layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:direction]; + _layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self]; _rangeController = [[ASRangeController alloc] init]; _rangeController.delegate = self; @@ -159,16 +167,29 @@ static BOOL _isInterceptedSelector(SEL sel) return self; } +- (void)dealloc +{ + // Sometimes the UIKit classes can call back to their delegate even during deallocation. + // This bug might be iOS 7-specific. + super.delegate = nil; + super.dataSource = nil; +} + #pragma mark - #pragma mark Overrides. -- (void)reloadData +- (void)reloadDataWithCompletion:(void (^)())completion { ASDisplayNodeAssert(self.asyncDelegate, @"ASCollectionView's asyncDelegate property must be set."); ASDisplayNodePerformBlockOnMainThread(^{ [super reloadData]; }); - [_dataController reloadDataWithAnimationOption:kASCollectionViewAnimationNone]; + [_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion]; +} + +- (void)reloadData +{ + [self reloadDataWithCompletion:nil]; } - (void)setDataSource:(id)dataSource @@ -184,13 +205,15 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)setAsyncDataSource:(id)asyncDataSource { - if (_asyncDataSource == asyncDataSource) - return; + // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle + // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource + // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out + // super.dataSource in this case because calls to _ASTableViewProxy will start failing and cause crashes. if (asyncDataSource == nil) { + super.dataSource = nil; _asyncDataSource = nil; _proxyDataSource = nil; - super.dataSource = nil; } else { _asyncDataSource = asyncDataSource; _proxyDataSource = [[_ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; @@ -200,13 +223,17 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)setAsyncDelegate:(id)asyncDelegate { - if (_asyncDelegate == asyncDelegate) - return; + // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle + // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate + // will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out + // super.delegate in this case because calls to _ASTableViewProxy will start failing and cause crashes. if (asyncDelegate == nil) { + // order is important here, the delegate must be callable while nilling super.delegate to avoid random crashes + // in UIScrollViewAccessibility. + super.delegate = nil; _asyncDelegate = nil; _proxyDelegate = nil; - super.delegate = nil; } else { _asyncDelegate = asyncDelegate; _proxyDelegate = [[_ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; @@ -263,42 +290,42 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)insertSections:(NSIndexSet *)sections { - [_dataController insertSections:sections withAnimationOption:kASCollectionViewAnimationNone]; + [_dataController insertSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)deleteSections:(NSIndexSet *)sections { - [_dataController deleteSections:sections withAnimationOption:kASCollectionViewAnimationNone]; + [_dataController deleteSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)reloadSections:(NSIndexSet *)sections { - [_dataController reloadSections:sections withAnimationOption:kASCollectionViewAnimationNone]; + [_dataController reloadSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { - [_dataController moveSection:section toSection:newSection withAnimationOption:kASCollectionViewAnimationNone]; + [_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths { - [_dataController insertRowsAtIndexPaths:indexPaths withAnimationOption:kASCollectionViewAnimationNone]; + [_dataController insertRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths { - [_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOption:kASCollectionViewAnimationNone]; + [_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths { - [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOption:kASCollectionViewAnimationNone]; + [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOption:kASCollectionViewAnimationNone]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; } - (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath @@ -340,24 +367,57 @@ static BOOL _isInterceptedSelector(SEL sel) - (ASScrollDirection)scrollDirection { CGPoint scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; + return [self scrollDirectionForVelocity:scrollVelocity]; +} + +- (ASScrollDirection)scrollDirectionForVelocity:(CGPoint)scrollVelocity +{ ASScrollDirection direction = ASScrollDirectionNone; - if (_layoutController.layoutDirection == ASFlowLayoutDirectionHorizontal) { - if (scrollVelocity.x > 0) { - direction = ASScrollDirectionRight; - } else if (scrollVelocity.x < 0) { - direction = ASScrollDirectionLeft; - } - } else { - if (scrollVelocity.y > 0) { - direction = ASScrollDirectionDown; + ASScrollDirection scrollableDirections = [self scrollableDirections]; + + if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. + if (scrollVelocity.x >= 0) { + direction |= ASScrollDirectionRight; } else { - direction = ASScrollDirectionUp; + direction |= ASScrollDirectionLeft; + } + } + if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. + if (scrollVelocity.y >= 0) { + direction |= ASScrollDirectionDown; + } else { + direction |= ASScrollDirectionUp; } } return direction; } +- (ASScrollDirection)scrollableDirections +{ + if ([self.collectionViewLayout asdk_isFlowLayout]) { + return [self flowLayoutScrollableDirections:(UICollectionViewFlowLayout *)self.collectionViewLayout]; + } else { + return [self nonFlowLayoutScrollableDirections]; + } +} + +- (ASScrollDirection)flowLayoutScrollableDirections:(UICollectionViewFlowLayout *)flowLayout { + return (flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASScrollDirectionHorizontalDirections : ASScrollDirectionVerticalDirections; +} + +- (ASScrollDirection)nonFlowLayoutScrollableDirections +{ + ASScrollDirection scrollableDirection = ASScrollDirectionNone; + if (self.contentSize.width > self.bounds.size.width) { // Can scroll horizontally. + scrollableDirection |= ASScrollDirectionHorizontalDirections; + } + if (self.contentSize.height > self.bounds.size.height) { // Can scroll vertically. + scrollableDirection |= ASScrollDirectionVerticalDirections; + } + return scrollableDirection; +} + - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; @@ -430,7 +490,7 @@ static BOOL _isInterceptedSelector(SEL sel) { CGSize restrainedSize = self.bounds.size; - if (_layoutController.layoutDirection == ASFlowLayoutDirectionHorizontal) { + if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) { restrainedSize.width = FLT_MAX; } else { restrainedSize.height = FLT_MAX; @@ -514,7 +574,7 @@ static BOOL _isInterceptedSelector(SEL sel) return [_dataController nodesAtIndexPaths:indexPaths]; } -- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); if (_performingBatchUpdates) { @@ -528,7 +588,7 @@ static BOOL _isInterceptedSelector(SEL sel) } } -- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -543,7 +603,7 @@ static BOOL _isInterceptedSelector(SEL sel) } } -- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -558,7 +618,7 @@ static BOOL _isInterceptedSelector(SEL sel) } } -- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.m index f423c33606..8a5d89ed11 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.m @@ -226,7 +226,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v if (!eventDispatchTable) { // Create the dispatch table for this event. - eventDispatchTable = [NSMapTable strongToStrongObjectsMapTable]; + eventDispatchTable = [NSMapTable weakToStrongObjectsMapTable]; [_controlEventDispatchTable setObject:eventDispatchTable forKey:eventKey]; } diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 303f6c1a18..fec0527f27 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -13,6 +13,8 @@ #import #import +#import +#import /** * The subclass header _ASDisplayNode+Subclasses_ defines the following methods that either must or can be overriden by @@ -34,7 +36,7 @@ * variables. */ -@interface ASDisplayNode (Subclassing) +@interface ASDisplayNode (Subclassing) /** @name View Configuration */ @@ -65,6 +67,26 @@ */ @property (nonatomic, readonly, assign, getter=isInHierarchy) BOOL inHierarchy; +/** + * @abstract Return the calculated layout. + * + * @discussion For node subclasses that implement manual layout (e.g., they have a custom -layout method), + * calculatedLayout may be accessed on subnodes to retrieved cached information about their size. + * This allows -layout to be very fast, saving time on the main thread. + * Note: .calculatedLayout will only be set for nodes that have had -measure: called on them. + * For manual layout, make sure you call -measure: in your implementation of -calculateSizeThatFits:. + * + * For node subclasses that use automatic layout (e.g., they implement -layoutSpecThatFits:), + * it is typically not necessary to use .calculatedLayout at any point. For these nodes, + * the ASLayoutSpec implementation will automatically call -measureWithSizeRange: on all of the subnodes, + * and the ASDisplayNode base class implementation of -layout will automatically make use of .calculatedLayout on the subnodes. + * + * @return Layout that wraps calculated size returned by -calculateSizeThatFits: (in manual layout mode), + * or layout already calculated from layout spec returned by -layoutSpecThatFits: (in automatic layout mode). + * + * @warning Subclasses must not override this; it returns the last cached layout and is never expensive. + */ +@property (nonatomic, readonly, assign) ASLayout *calculatedLayout; /** @name View Lifecycle */ @@ -96,8 +118,39 @@ - (void)layoutDidFinish; -/** @name Sizing */ +/** @name Layout calculation */ +/** + * @abstract Asks the node to measure a layout based on given size range. + * + * @param constrainedSize The minimum and maximum sizes the receiver should fit in. + * + * @return An ASLayout instance defining the layout of the receiver (and its children, if the box layout model is used). + * + * @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the + * constraint and the result. + * + * @warning Subclasses must not override this; it caches results from -calculateLayoutThatFits:. Calling this method may + * be expensive if result is not cached. + * + * @see [ASDisplayNode(Subclassing) calculateLayoutThatFits:] + */ +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize; + +/** + * @abstract Calculate a layout based on given size range. + * + * @param constrainedSize The minimum and maximum sizes the receiver should fit in. + * + * @return An ASLayout instance defining the layout of the receiver (and its children, if the box layout model is used). + * + * @discussion This method is called on a non-main thread. The default implementation calls either -layoutSpecThatFits: + * or -calculateSizeThatFits:, whichever method is overriden. Subclasses rarely need to override this method, + * override -layoutSpecThatFits: or -calculateSizeThatFits: instead. + * + * @note This method should not be called directly outside of ASDisplayNode; use -measure: or -calculatedLayout instead. + */ +- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize; /** * @abstract Return the calculated size. @@ -105,21 +158,37 @@ * @param constrainedSize The maximum size the receiver should fit in. * * @discussion Subclasses that override should expect this method to be called on a non-main thread. The returned size - * is cached by ASDisplayNode for quick access during -layout, via -calculatedSize. Other expensive work that needs to + * is wrapped in an ASLayout and cached for quick access during -layout. Other expensive work that needs to * be done before display can be performed here, and using ivars to cache any valuable intermediate results is * encouraged. * - * @note This method should not be called directly outside of ASDisplayNode; use -measure: or -calculatedSize instead. + * @note Subclasses that override are committed to manual layout. Therefore, -layout: must be overriden to layout all subnodes or subviews. + * + * @note This method should not be called directly outside of ASDisplayNode; use -measure: or -calculatedLayout instead. */ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize; /** - * @abstract Invalidate previously measured and cached size. + * @abstract Return a layout spec that describes the layout of the receiver and its children. * - * @discussion Subclasses should call this method to invalidate the previously measured and cached size for the display + * @param constrainedSize The minimum and maximum sizes the receiver should fit in. + * + * @discussion Subclasses that override should expect this method to be called on a non-main thread. The returned layout spec + * is used to calculate an ASLayout and cached by ASDisplayNode for quick access during -layout. Other expensive work that needs to + * be done before display can be performed here, and using ivars to cache any valuable intermediate results is + * encouraged. + * + * @note This method should not be called directly outside of ASDisplayNode; use -measure: or -calculatedLayout instead. + */ +- (id)layoutSpecThatFits:(ASSizeRange)constrainedSize; + +/** + * @abstract Invalidate previously measured and cached layout. + * + * @discussion Subclasses should call this method to invalidate the previously measured and cached layout for the display * node, when the contents of the node change in such a way as to require measuring it again. */ -- (void)invalidateCalculatedSize; +- (void)invalidateCalculatedLayout; /** @name Drawing */ @@ -190,8 +259,9 @@ * * @discussion Subclasses may override this method to be notified when they should begin to fetch data. Fetching * should be done asynchronously. The node is also responsible for managing the memory of any data. + * The data may be remote and accessed via the network, but could also be a local database query. */ -- (void)fetchRemoteData ASDISPLAYNODE_REQUIRES_SUPER; +- (void)fetchData ASDISPLAYNODE_REQUIRES_SUPER; /** * @abstract Indicates that the receiver is about to display its subnodes. This method is not called if there are no @@ -206,8 +276,8 @@ * @abstract Indicates that the receiver is finished displaying its subnodes. This method is not called if there are * no subnodes present. * - * @discussion Subclasses may override this method to be notified when subnode display (asynchronous or synchronous) is - * about to begin. + * @discussion Subclasses may override this method to be notified when subnode display (asynchronous or synchronous) has + * completed. */ - (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode ASDISPLAYNODE_REQUIRES_SUPER; @@ -323,18 +393,18 @@ * Provides an opportunity to clear backing store and other memory-intensive intermediates, such as text layout managers * on the current node. * - * @discussion Called by -recursivelyClearRendering. Base class implements self.contents = nil, clearing any backing + * @discussion Called by -recursivelyClearContents. Base class implements self.contents = nil, clearing any backing * store, for asynchronous regeneration when needed. */ -- (void)clearRendering ASDISPLAYNODE_REQUIRES_SUPER; +- (void)clearContents ASDISPLAYNODE_REQUIRES_SUPER; /** - * Provides an opportunity to clear any remote data on the current node. + * Provides an opportunity to clear any fetched data (e.g. remote / network or database-queried) on the current node. * - * @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearRemoteData or - * selectively clear remote data. + * @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearFetchedData or + * selectively clear fetched data. */ -- (void)clearRemoteData ASDISPLAYNODE_REQUIRES_SUPER; +- (void)clearFetchedData ASDISPLAYNODE_REQUIRES_SUPER; /** @name Placeholders */ diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 2fed3d8fb8..bd0c5727ab 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -11,7 +11,7 @@ #import #import #import - +#import typedef UIView *(^ASDisplayNodeViewBlock)(); typedef CALayer *(^ASDisplayNodeLayerBlock)(); @@ -118,8 +118,8 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); /** @name Managing dimensions */ -/** - * @abstract Asks the node to calculate and return the size that best fits its subnodes. +/** + * @abstract Asks the node to measure and return the size that best fits its subnodes. * * @param constrainedSize The maximum size the receiver should fit in. * @@ -128,10 +128,12 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); * @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the * constraint and the result. * - * @warning Subclasses must not override this; it caches results from -calculateSizeThatFits:. Calling this method may + * @warning Subclasses must not override this; it calls -measureWithSizeRange: with zero min size. + * -measureWithSizeRange: caches results from -calculateLayoutThatFits:. Calling this method may * be expensive if result is not cached. * - * @see [ASDisplayNode(Subclassing) calculateSizeThatFits:] + * @see [ASDisplayNode(Subclassing) measureWithSizeRange:] + * @see [ASDisplayNode(Subclassing) calculateLayoutThatFits:] */ - (CGSize)measure:(CGSize)constrainedSize; @@ -139,21 +141,20 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); * @abstract Return the calculated size. * * @discussion Ideal for use by subclasses in -layout, having already prompted their subnodes to calculate their size by - * calling -measure: on them in -calculateSizeThatFits:. + * calling -measure: on them in -calculateLayoutThatFits. * - * @return Size already calculated by calculateSizeThatFits:. + * @return Size already calculated by -calculateLayoutThatFits:. * - * @warning Subclasses must not override this; it returns the last cached size calculated and is never expensive. + * @warning Subclasses must not override this; it returns the last cached measurement and is never expensive. */ @property (nonatomic, readonly, assign) CGSize calculatedSize; /** - * @abstract Return the constrained size used for calculating size. + * @abstract Return the constrained size range used for calculating layout. * - * @return The constrained size used by calculateSizeThatFits:. + * @return The minimum and maximum constrained sizes used by calculateLayoutThatFits:. */ -@property (nonatomic, readonly, assign) CGSize constrainedSizeForCalculatedSize; - +@property (nonatomic, readonly, assign) ASSizeRange constrainedSizeForCalculatedLayout; /** @name Managing the nodes hierarchy */ @@ -312,7 +313,7 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); - (void)recursivelySetDisplaySuspended:(BOOL)flag; /** - * @abstract Calls -clearRendering on the receiver and its subnode hierarchy. + * @abstract Calls -clearContents on the receiver and its subnode hierarchy. * * @discussion Clears backing stores and other memory-intensive intermediates. * If the node is removed from a visible hierarchy and then re-added, it will automatically trigger a new asynchronous display, @@ -322,27 +323,27 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); * @see displaySuspended and setNeedsDisplay */ -- (void)recursivelyClearRendering; +- (void)recursivelyClearContents; /** - * @abstract Calls -clearRemoteData on the receiver and its subnode hierarchy. + * @abstract Calls -clearFetchedData on the receiver and its subnode hierarchy. * * @discussion Clears any memory-intensive fetched content. * This method is used to notify the node that it should purge any content that is both expensive to fetch and to * retain in memory. * - * @see clearRemoteData and fetchRemoteData + * @see clearFetchedData and fetchData */ -- (void)recursivelyClearRemoteData; +- (void)recursivelyClearFetchedData; /** - * @abstract Calls -fetchRemoteData on the receiver and its subnode hierarchy. + * @abstract Calls -fetchData on the receiver and its subnode hierarchy. * * @discussion Fetches content from remote sources for the current node and all subnodes. * - * @see fetchRemoteData and clearRemoteData + * @see fetchData and clearFetchedData */ -- (void)recursivelyFetchRemoteData; +- (void)recursivelyFetchData; /** * @abstract Toggle displaying a placeholder over the node that covers content until the node and all subnodes are @@ -432,6 +433,16 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); */ - (CGRect)convertRect:(CGRect)rect fromNode:(ASDisplayNode *)node; +/** @name UIResponder methods */ + +// By default these fall through to the underlying view, but can be overridden. +- (BOOL)canBecomeFirstResponder; // default==NO +- (BOOL)becomeFirstResponder; // default==NO (no-op) +- (BOOL)canResignFirstResponder; // default==YES +- (BOOL)resignFirstResponder; // default==NO (no-op) +- (BOOL)isFirstResponder; +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender; + @end @@ -533,6 +544,9 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); @property (atomic, assign) BOOL accessibilityViewIsModal; @property (atomic, assign) BOOL shouldGroupAccessibilityChildren; +// Accessibility identification support +@property (nonatomic, copy) NSString *accessibilityIdentifier; + @end /* diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 614e4c833b..bf81cdc960 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -18,6 +18,8 @@ #import "_ASScopeTimer.h" #import "ASDisplayNodeExtras.h" +#import "ASInternalHelpers.h" + @interface ASDisplayNode () /** @@ -37,37 +39,16 @@ @implementation ASDisplayNode +@synthesize spacingBefore = _spacingBefore; +@synthesize spacingAfter = _spacingAfter; +@synthesize flexGrow = _flexGrow; +@synthesize flexShrink = _flexShrink; +@synthesize flexBasis = _flexBasis; +@synthesize alignSelf = _alignSelf; + BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) { - Method superclassMethod = class_getInstanceMethod([ASDisplayNode class], selector); - Method subclassMethod = class_getInstanceMethod(subclass, selector); - IMP superclassIMP = superclassMethod ? method_getImplementation(superclassMethod) : NULL; - IMP subclassIMP = subclassMethod ? method_getImplementation(subclassMethod) : NULL; - - return (superclassIMP != subclassIMP); -} - -CGFloat ASDisplayNodeScreenScale() -{ - static CGFloat screenScale = 0.0; - static dispatch_once_t onceToken; - ASDispatchOnceOnMainThread(&onceToken, ^{ - screenScale = [[UIScreen mainScreen] scale]; - }); - return screenScale; -} - -static void ASDispatchOnceOnMainThread(dispatch_once_t *predicate, dispatch_block_t block) -{ - if ([NSThread isMainThread]) { - dispatch_once(predicate, block); - } else { - if (DISPATCH_EXPECT(*predicate == 0L, NO)) { - dispatch_sync(dispatch_get_main_queue(), ^{ - dispatch_once(predicate, block); - }); - } - } + return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); } void ASDisplayNodePerformBlockOnMainThread(void (^block)()) @@ -89,9 +70,17 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)()) // 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(recursivelyClearRendering)), @"Subclass %@ must not override recursivelyClearRendering method", NSStringFromClass(self)); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearRemoteData)), @"Subclass %@ must not override recursivelyClearRemoteData 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)); } + (BOOL)layerBackedNodesEnabled @@ -113,10 +102,10 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)()) - (void)_initializeInstance { - _contentsScaleForDisplay = ASDisplayNodeScreenScale(); + _contentsScaleForDisplay = ASScreenScale(); _displaySentinel = [[ASSentinel alloc] init]; - + _flags.isInHierarchy = NO; _flags.displaysAsynchronously = YES; @@ -138,7 +127,12 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)()) if (ASDisplayNodeSubclassOverridesSelector([self class], @selector(touchesEnded:withEvent:))) { overrides |= ASDisplayNodeMethodOverrideTouchesEnded; } + if (ASDisplayNodeSubclassOverridesSelector([self class], @selector(calculateSizeThatFits:))) { + overrides |= ASDisplayNodeMethodOverrideCalculateSizeThatFits; + } _methodOverrides = overrides; + + _flexBasis = ASRelativeDimensionUnconstrained; } - (id)init @@ -437,33 +431,38 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)()) - (CGSize)measure:(CGSize)constrainedSize { - ASDN::MutexLocker l(_propertyLock); - return [self __measure:constrainedSize]; + return [self measureWithSizeRange:ASSizeRangeMake(CGSizeZero, constrainedSize)].size; } -- (CGSize)__measure:(CGSize)constrainedSize +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + ASDN::MutexLocker l(_propertyLock); + return [self __measureWithSizeRange:constrainedSize]; +} + +- (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize { ASDisplayNodeAssertThreadAffinity(self); if (![self __shouldSize]) - return CGSizeZero; + return nil; // only calculate the size if // - we haven't already - // - the width is different from the last time - // - the height is different from the last time - if (!_flags.isMeasured || !CGSizeEqualToSize(constrainedSize, _constrainedSize)) { - _size = [self calculateSizeThatFits:constrainedSize]; + // - the constrained size range is different + if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) { + _layout = [self calculateLayoutThatFits:constrainedSize]; _constrainedSize = constrainedSize; _flags.isMeasured = YES; } - ASDisplayNodeAssertTrue(_size.width >= 0.0); - ASDisplayNodeAssertTrue(_size.height >= 0.0); + ASDisplayNodeAssertTrue(_layout.layoutableObject == self); + ASDisplayNodeAssertTrue(_layout.size.width >= 0.0); + ASDisplayNodeAssertTrue(_layout.size.height >= 0.0); - // we generate placeholders at measure: time so that a node is guaranteed to have a placeholder ready to go + // we generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go // also if a node has no size, it should not have a placeholder - if (self.placeholderEnabled && [self _displaysAsynchronously] && _size.width > 0.0 && _size.height > 0.0) { + if (self.placeholderEnabled && [self _displaysAsynchronously] && _layout.size.width > 0.0 && _layout.size.height > 0.0) { if (!_placeholderImage) { _placeholderImage = [self placeholderImage]; } @@ -473,7 +472,7 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)()) } } - return _size; + return _layout; } - (BOOL)displaysAsynchronously @@ -566,7 +565,7 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)()) ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(_propertyLock); if (CGRectEqualToRect(_layer.bounds, CGRectZero)) { - return; // Performing layout on a zero-bounds view often results in frame calculations with negative sizes after applying margins, which will cause measure: on subnodes to assert. + 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; [self layout]; @@ -1216,6 +1215,10 @@ static NSInteger incrementIfFound(NSInteger i) { { ASDN::MutexLocker l(_propertyLock); + if (!_pendingDisplayNodes) { + _pendingDisplayNodes = [[NSMutableSet alloc] init]; + } + [_pendingDisplayNodes addObject:node]; } @@ -1278,19 +1281,51 @@ static NSInteger incrementIfFound(NSInteger i) { #pragma mark - For Subclasses +- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize +{ + ASDisplayNodeAssertThreadAffinity(self); + if (_methodOverrides & ASDisplayNodeMethodOverrideCalculateSizeThatFits) { + CGSize size = [self calculateSizeThatFits:constrainedSize.max]; + return [ASLayout newWithLayoutableObject:self size:ASSizeRangeClamp(constrainedSize, size)]; + } else { + id layoutSpec = [self layoutSpecThatFits:constrainedSize]; + ASLayout *layout = [layoutSpec measureWithSizeRange:constrainedSize]; + // Make sure layoutableObject of the root layout is `self`, so that the flattened layout will be structurally correct. + if (layout.layoutableObject != self) { + layout.position = CGPointZero; + layout = [ASLayout newWithLayoutableObject:self size:layout.size sublayouts:@[layout]]; + } + return [layout flattenedLayoutUsingPredicateBlock:^BOOL(ASLayout *evaluatedLayout) { + return [_subnodes containsObject:evaluatedLayout.layoutableObject]; + }]; + } +} + - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { ASDisplayNodeAssertThreadAffinity(self); return CGSizeZero; } +- (id)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASDisplayNodeAssertThreadAffinity(self); + return nil; +} + +- (ASLayout *)calculatedLayout +{ + ASDisplayNodeAssertThreadAffinity(self); + return _layout; +} + - (CGSize)calculatedSize { ASDisplayNodeAssertThreadAffinity(self); - return _size; + return _layout.size; } -- (CGSize)constrainedSizeForCalculatedSize +- (ASSizeRange)constrainedSizeForCalculatedLayout { ASDisplayNodeAssertThreadAffinity(self); return _constrainedSize; @@ -1301,10 +1336,10 @@ static NSInteger incrementIfFound(NSInteger i) { return nil; } -- (void)invalidateCalculatedSize +- (void)invalidateCalculatedLayout { ASDisplayNodeAssertThreadAffinity(self); - // This will cause -measure: to actually compute the size instead of returning the previously cached size + // This will cause -measureWithSizeRange: to actually compute the size instead of returning the previously cached size _flags.isMeasured = NO; } @@ -1329,49 +1364,63 @@ static NSInteger incrementIfFound(NSInteger i) { [self __exitedHierarchy]; } -- (void)clearRendering +- (void)clearContents { self.layer.contents = nil; _placeholderLayer.contents = nil; + _placeholderImage = nil; } -- (void)recursivelyClearRendering +- (void)recursivelyClearContents { for (ASDisplayNode *subnode in self.subnodes) { - [subnode recursivelyClearRendering]; + [subnode recursivelyClearContents]; } - [self clearRendering]; + [self clearContents]; } -- (void)fetchRemoteData +- (void)fetchData { // subclass override } -- (void)recursivelyFetchRemoteData +- (void)recursivelyFetchData { for (ASDisplayNode *subnode in self.subnodes) { - [subnode recursivelyFetchRemoteData]; + [subnode recursivelyFetchData]; } - [self fetchRemoteData]; + [self fetchData]; } -- (void)clearRemoteData +- (void)clearFetchedData { // subclass override } -- (void)recursivelyClearRemoteData +- (void)recursivelyClearFetchedData { for (ASDisplayNode *subnode in self.subnodes) { - [subnode recursivelyClearRemoteData]; + [subnode recursivelyClearFetchedData]; } - [self clearRemoteData]; + [self clearFetchedData]; } - (void)layout { ASDisplayNodeAssertMainThread(); + + if (!_flags.isMeasured) { + return; + } + + // Assume that _layout was flattened and is 1-level deep. + for (ASLayout *subnodeLayout in _layout.sublayouts) { + ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Cached sublayouts must only contain subnodes' layout."); + ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = CGRectMake(subnodeLayout.position.x, + subnodeLayout.position.y, + subnodeLayout.size.width, + subnodeLayout.size.height); + } } - (void)displayWillStart @@ -1382,7 +1431,11 @@ static NSInteger incrementIfFound(NSInteger i) { [_supernode subnodeDisplayWillStart:self]; if (_placeholderImage && _placeholderLayer && self.layer.contents == nil) { + [CATransaction begin]; + [CATransaction setDisableActions:YES]; _placeholderLayer.contents = (id)_placeholderImage.CGImage; + _placeholderLayer.opacity = 1.0; + [CATransaction commit]; [self.layer addSublayer:_placeholderLayer]; } } @@ -1637,7 +1690,7 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, static dispatch_queue_t asyncSizingQueue = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - asyncSizingQueue = dispatch_queue_create("com.facebook.AsyncDisplayKit.ASDisplayNode.asyncSizingQueue", DISPATCH_QUEUE_CONCURRENT); + asyncSizingQueue = dispatch_queue_create("org.AsyncDisplayKit.ASDisplayNode.asyncSizingQueue", DISPATCH_QUEUE_CONCURRENT); // we use the highpri queue to prioritize UI rendering over other async operations dispatch_set_target_queue(asyncSizingQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); }); @@ -1689,6 +1742,35 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, } +- (BOOL)canBecomeFirstResponder { + return NO; +} + +- (BOOL)canResignFirstResponder { + return YES; +} + +- (BOOL)isFirstResponder { + ASDisplayNodeAssertMainThread(); + return _view != nil && [_view isFirstResponder]; +} + +// Note: this implicitly loads the view if it hasn't been loaded yet. +- (BOOL)becomeFirstResponder { + ASDisplayNodeAssertMainThread(); + return !self.layerBacked && [self canBecomeFirstResponder] && [self.view becomeFirstResponder]; +} + +- (BOOL)resignFirstResponder { + ASDisplayNodeAssertMainThread(); + return !self.layerBacked && [self canResignFirstResponder] && [_view resignFirstResponder]; +} + +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { + ASDisplayNodeAssertMainThread(); + return !self.layerBacked && [self.view canPerformAction:action withSender:sender]; +} + @end @implementation ASDisplayNode (Debugging) @@ -1816,12 +1898,12 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - (void)reclaimMemory { - [self clearRendering]; + [self clearContents]; } - (void)recursivelyReclaimMemory { - [self recursivelyClearRendering]; + [self recursivelyClearContents]; } @end diff --git a/AsyncDisplayKit/ASEditableTextNode.h b/AsyncDisplayKit/ASEditableTextNode.h index 11cfaf8405..92811e2457 100644 --- a/AsyncDisplayKit/ASEditableTextNode.h +++ b/AsyncDisplayKit/ASEditableTextNode.h @@ -57,10 +57,10 @@ - (BOOL)isFirstResponder; //! @abstract Makes the receiver's text view the first responder. -- (void)becomeFirstResponder; +- (BOOL)becomeFirstResponder; //! @abstract Resigns the receiver's text view from first-responder status, if it has it. -- (void)resignFirstResponder; +- (BOOL)resignFirstResponder; #pragma mark - Geometry /** diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index 0b757d1752..3761134481 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -152,7 +152,7 @@ - (void)layout { - [super layout]; + ASDisplayNodeAssertMainThread(); [self _layoutTextView]; } @@ -290,7 +290,7 @@ [_textKitComponents.textStorage setAttributedString:attributedStringToDisplay]; // Calculated size depends on the seeded text. - [self invalidateCalculatedSize]; + [self invalidateCalculatedLayout]; // Update if placeholder is shown. [self _updateDisplayingPlaceholder]; @@ -352,16 +352,26 @@ return [_textKitComponents.textView isFirstResponder]; } -- (void)becomeFirstResponder -{ - ASDN::MutexLocker l(_textKitLock); - [_textKitComponents.textView becomeFirstResponder]; +- (BOOL)canBecomeFirstResponder { + ASDN::MutexLocker l(_textKitLock); + return [_textKitComponents.textView canBecomeFirstResponder]; } -- (void)resignFirstResponder +- (BOOL)becomeFirstResponder { ASDN::MutexLocker l(_textKitLock); - [_textKitComponents.textView resignFirstResponder]; + return [_textKitComponents.textView becomeFirstResponder]; +} + +- (BOOL)canResignFirstResponder { + ASDN::MutexLocker l(_textKitLock); + return [_textKitComponents.textView canResignFirstResponder]; +} + +- (BOOL)resignFirstResponder +{ + ASDN::MutexLocker l(_textKitLock); + return [_textKitComponents.textView resignFirstResponder]; } #pragma mark - UITextView Delegate @@ -389,7 +399,7 @@ [self _updateDisplayingPlaceholder]; // Invalidate, as our calculated size depends on the textview's seeded text. - [self invalidateCalculatedSize]; + [self invalidateCalculatedLayout]; // Delegateify. [self _delegateDidUpdateText]; diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 76bd5b6cb4..d7cdebbd94 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -17,6 +17,8 @@ #import "ASImageNode+CGExtras.h" +#import "ASInternalHelpers.h" + @interface _ASImageNodeDrawParameters : NSObject @property (nonatomic, assign, readonly) BOOL cropEnabled; @@ -82,7 +84,7 @@ return nil; // TODO can this be removed? - self.contentsScale = ASDisplayNodeScreenScale(); + self.contentsScale = ASScreenScale(); self.contentMode = UIViewContentModeScaleAspectFill; self.opaque = NO; @@ -123,7 +125,7 @@ ASDN::MutexUnlocker u(_imageLock); ASDisplayNodePerformBlockOnMainThread(^{ - [self invalidateCalculatedSize]; + [self invalidateCalculatedLayout]; [self setNeedsDisplay]; }); } @@ -347,7 +349,7 @@ extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock( // Draw a border on top. if (borderWidth > 0.0) { [borderColor setStroke]; - CGContextSetLineWidth(UIGraphicsGetCurrentContext(), borderWidth); + [roundOutline setLineWidth:borderWidth]; [roundOutline stroke]; } diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index b618228ce4..0bc9c6e8f8 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -162,9 +162,9 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent } #pragma mark - ASDisplayNode Overrides -- (void)clearRendering +- (void)clearContents { - [super clearRendering]; // This actually clears the contents, so we need to do this first for our displayedImageIdentifier to be meaningful. + [super clearContents]; // This actually clears the contents, so we need to do this first for our displayedImageIdentifier to be meaningful. [self _setDisplayedImageIdentifier:nil withImage:nil]; if (_downloadIdentifier) { @@ -177,12 +177,12 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent { [super displayWillStart]; - [self fetchRemoteData]; + [self fetchData]; } -- (void)fetchRemoteData +- (void)fetchData { - [super fetchRemoteData]; + [super fetchData]; [self _loadImageIdentifiers]; } diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index c21d5ea402..658176eeee 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -127,12 +127,12 @@ { [super displayWillStart]; - [self fetchRemoteData]; + [self fetchData]; } -- (void)clearRemoteData +- (void)clearFetchedData { - [super clearRemoteData]; + [super clearFetchedData]; { ASDN::MutexLocker l(_lock); @@ -143,9 +143,9 @@ } } -- (void)fetchRemoteData +- (void)fetchData { - [super fetchRemoteData]; + [super fetchData]; { ASDN::MutexLocker l(_lock); diff --git a/AsyncDisplayKit/ASScrollNode.m b/AsyncDisplayKit/ASScrollNode.m index 3b19d126e2..ade0ee9e5e 100644 --- a/AsyncDisplayKit/ASScrollNode.m +++ b/AsyncDisplayKit/ASScrollNode.m @@ -7,6 +7,19 @@ */ #import "ASScrollNode.h" +#import "_ASDisplayLayer.h" + +@interface ASScrollView : UIScrollView +@end + +@implementation ASScrollView + ++ (Class)layerClass +{ + return [_ASDisplayLayer class]; +} + +@end @implementation ASScrollNode @dynamic view; @@ -14,7 +27,7 @@ - (instancetype)init { return [super initWithViewBlock:^UIView *{ - return [[UIScrollView alloc] init]; + return [[ASScrollView alloc] init]; }]; } diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index f783a5e880..8b399a01b4 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -70,6 +70,15 @@ */ @property (nonatomic, assign) CGFloat leadingScreensForBatching; +/** + * Reload everything from scratch, destroying the working range and all cached nodes. + * + * @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on + * the main thread. + * @warning This method is substantially more expensive than UITableView's version. + */ +-(void)reloadDataWithCompletion:(void (^)())completion; + /** * Reload everything from scratch, destroying the working range and all cached nodes. * diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 4c2b79ca01..b5ff1fb48f 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -10,7 +10,7 @@ #import "ASAssert.h" #import "ASDataController.h" -#import "ASFlowLayoutController.h" +#import "ASCollectionViewLayoutController.h" #import "ASLayoutController.h" #import "ASRangeController.h" #import "ASDisplayNodeInternal.h" @@ -77,11 +77,17 @@ static BOOL _isInterceptedSelector(SEL sel) - (BOOL)respondsToSelector:(SEL)aSelector { + ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early + ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); + return (_isInterceptedSelector(aSelector) || [_target respondsToSelector:aSelector]); } - (id)forwardingTargetForSelector:(SEL)aSelector { + ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early + ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); + if (_isInterceptedSelector(aSelector)) { return _interceptor; } @@ -118,6 +124,8 @@ static BOOL _isInterceptedSelector(SEL sel) BOOL _asyncDataFetchingEnabled; ASBatchContext *_batchContext; + + NSIndexPath *_pendingVisibleIndexPath; } @property (atomic, assign) BOOL asyncDataSourceLocked; @@ -126,9 +134,50 @@ static BOOL _isInterceptedSelector(SEL sel) @implementation ASTableView +/** + @summary Conditionally performs UIView geometry changes in the given block without animation. + + Used primarily to circumvent UITableView forcing insertion animations when explicitly told not to via + `UITableViewRowAnimationNone`. More info: https://github.com/facebook/AsyncDisplayKit/pull/445 + + @param withoutAnimation Set to `YES` to perform given block without animation + @param block Perform UIView geometry changes within the passed block + */ +void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { + if (withoutAnimation) { + BOOL animationsEnabled = [UIView areAnimationsEnabled]; + [UIView setAnimationsEnabled:NO]; + block(); + [UIView setAnimationsEnabled:animationsEnabled]; + } else { + block(); + } +} + #pragma mark - #pragma mark Lifecycle +- (void)configureWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled +{ + _layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical]; + + _rangeController = [[ASRangeController alloc] init]; + _rangeController.layoutController = _layoutController; + _rangeController.delegate = self; + + _dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled]; + _dataController.dataSource = self; + _dataController.delegate = _rangeController; + + _layoutController.dataSource = _dataController; + + _asyncDataFetchingEnabled = asyncDataFetchingEnabled; + _asyncDataSourceLocked = NO; + + _leadingScreensForBatching = 1.0; + _batchContext = [[ASBatchContext alloc] init]; +} + - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style { return [self initWithFrame:frame style:style asyncDataFetching:NO]; @@ -136,29 +185,36 @@ static BOOL _isInterceptedSelector(SEL sel) - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled { - if (!(self = [super initWithFrame:frame style:style])) return nil; - _layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical]; - - _rangeController = [[ASRangeController alloc] init]; - _rangeController.layoutController = _layoutController; - _rangeController.delegate = self; - - _dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled]; - _dataController.dataSource = self; - _dataController.delegate = _rangeController; - - _asyncDataFetchingEnabled = asyncDataFetchingEnabled; - _asyncDataSourceLocked = NO; - - _leadingScreensForBatching = 1.0; - _batchContext = [[ASBatchContext alloc] init]; + // FIXME: asyncDataFetching is currently unreliable for some use cases. + // https://github.com/facebook/AsyncDisplayKit/issues/385 + asyncDataFetchingEnabled = NO; + + [self configureWithAsyncDataFetching:asyncDataFetchingEnabled]; return self; } +- (instancetype)initWithCoder:(NSCoder *)aDecoder +{ + if (!(self = [super initWithCoder:aDecoder])) + return nil; + + [self configureWithAsyncDataFetching:NO]; + + return self; +} + +- (void)dealloc +{ + // Sometimes the UIKit classes can call back to their delegate even during deallocation. + // This bug might be iOS 7-specific. + super.delegate = nil; + super.dataSource = nil; +} + #pragma mark - #pragma mark Overrides @@ -175,13 +231,15 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)setAsyncDataSource:(id)asyncDataSource { - if (_asyncDataSource == asyncDataSource) - return; + // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle + // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource + // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out + // super.dataSource in this case because calls to _ASTableViewProxy will start failing and cause crashes. if (asyncDataSource == nil) { + super.dataSource = nil; _asyncDataSource = nil; _proxyDataSource = nil; - super.dataSource = nil; } else { _asyncDataSource = asyncDataSource; _proxyDataSource = [[_ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; @@ -191,13 +249,17 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)setAsyncDelegate:(id)asyncDelegate { - if (_asyncDelegate == asyncDelegate) - return; + // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle + // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate + // will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out + // super.delegate in this case because calls to _ASTableViewProxy will start failing and cause crashes. if (asyncDelegate == nil) { - _asyncDelegate = nil; - _proxyDelegate = nil; + // order is important here, the delegate must be callable while nilling super.delegate to avoid random crashes + // in UIScrollViewAccessibility. super.delegate = nil; + _asyncDelegate = nil; + _proxyDelegate = nil; } else { _asyncDelegate = asyncDelegate; _proxyDelegate = [[_ASTableViewProxy alloc] initWithTarget:asyncDelegate interceptor:self]; @@ -205,13 +267,18 @@ static BOOL _isInterceptedSelector(SEL sel) } } -- (void)reloadData +- (void)reloadDataWithCompletion:(void (^)())completion { ASDisplayNodeAssert(self.asyncDelegate, @"ASTableView's asyncDelegate property must be set."); ASDisplayNodePerformBlockOnMainThread(^{ [super reloadData]; }); - [_dataController reloadDataWithAnimationOption:UITableViewRowAnimationNone]; + [_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion]; +} + +- (void)reloadData +{ + [self reloadDataWithCompletion:nil]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -268,42 +335,42 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { - [_dataController insertSections:sections withAnimationOption:animation]; + [_dataController insertSections:sections withAnimationOptions:animation]; } - (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { - [_dataController deleteSections:sections withAnimationOption:animation]; + [_dataController deleteSections:sections withAnimationOptions:animation]; } - (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { - [_dataController reloadSections:sections withAnimationOption:animation]; + [_dataController reloadSections:sections withAnimationOptions:animation]; } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { - [_dataController moveSection:section toSection:newSection withAnimationOption:UITableViewRowAnimationNone]; + [_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; } - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { - [_dataController insertRowsAtIndexPaths:indexPaths withAnimationOption:animation]; + [_dataController insertRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; } - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { - [_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOption:animation]; + [_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; } - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { - [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOption:animation]; + [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; } - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOption:UITableViewRowAnimationNone]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; } #pragma mark - @@ -324,6 +391,11 @@ static BOOL _isInterceptedSelector(SEL sel) cell.backgroundColor = node.backgroundColor; cell.selectionStyle = node.selectionStyle; + // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) + // This is actually a workaround for a bug we are seeing in some rare cases (selected background view + // overlaps other cells if size of ASCellNode has changed.) + cell.clipsToBounds = node.clipsToBounds; + return cell; } @@ -347,25 +419,19 @@ static BOOL _isInterceptedSelector(SEL sel) { CGPoint scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; ASScrollDirection direction = ASScrollDirectionNone; - if (_layoutController.layoutDirection == ASFlowLayoutDirectionHorizontal) { - if (scrollVelocity.x > 0) { - direction = ASScrollDirectionRight; - } else if (scrollVelocity.x < 0) { - direction = ASScrollDirectionLeft; - } + if (scrollVelocity.y > 0) { + direction = ASScrollDirectionDown; } else { - if (scrollVelocity.y > 0) { - direction = ASScrollDirectionDown; - } else { - direction = ASScrollDirectionUp; - } + direction = ASScrollDirectionUp; } - + return direction; } - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { + _pendingVisibleIndexPath = indexPath; + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) { @@ -375,6 +441,10 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath { + if ([_pendingVisibleIndexPath isEqual:indexPath]) { + _pendingVisibleIndexPath = nil; + } + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNodeForRowAtIndexPath:)]) { @@ -445,7 +515,51 @@ static BOOL _isInterceptedSelector(SEL sel) - (NSArray *)rangeControllerVisibleNodeIndexPaths:(ASRangeController *)rangeController { ASDisplayNodeAssertMainThread(); - return [self indexPathsForVisibleRows]; + + NSArray *visibleIndexPaths = self.indexPathsForVisibleRows; + + if ( _pendingVisibleIndexPath ) { + NSMutableSet *indexPaths = [NSMutableSet setWithArray:self.indexPathsForVisibleRows]; + + BOOL (^isAfter)(NSIndexPath *, NSIndexPath *) = ^BOOL(NSIndexPath *indexPath, NSIndexPath *anchor) { + if (!anchor || !indexPath) { + return NO; + } + if (indexPath.section == anchor.section) { + return (indexPath.row == anchor.row+1); // assumes that indexes are valid + + } else if (indexPath.section > anchor.section && indexPath.row == 0) { + if (anchor.row != [_dataController numberOfRowsInSection:anchor.section] -1) { + return NO; // anchor is not at the end of the section + } + + NSInteger nextSection = anchor.section+1; + while([_dataController numberOfRowsInSection:nextSection] == 0) { + ++nextSection; + } + + return indexPath.section == nextSection; + } + + return NO; + }; + + BOOL (^isBefore)(NSIndexPath *, NSIndexPath *) = ^BOOL(NSIndexPath *indexPath, NSIndexPath *anchor) { + return isAfter(anchor, indexPath); + }; + + if ( [indexPaths containsObject:_pendingVisibleIndexPath]) { + _pendingVisibleIndexPath = nil; // once it has shown up in visibleIndexPaths, we can stop tracking it + } else if (!isBefore(_pendingVisibleIndexPath, visibleIndexPaths.firstObject) && + !isAfter(_pendingVisibleIndexPath, visibleIndexPaths.lastObject)) { + _pendingVisibleIndexPath = nil; // not contiguous, ignore. + } else { + [indexPaths addObject:_pendingVisibleIndexPath]; + visibleIndexPaths = [indexPaths.allObjects sortedArrayUsingSelector:@selector(compare:)]; + } + } + + return visibleIndexPaths; } - (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths @@ -459,32 +573,44 @@ static BOOL _isInterceptedSelector(SEL sel) return self.bounds.size; } -- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOption]; + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }); } -- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOption]; + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }); } -- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - [super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOption]; + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + [super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }); } -- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - [super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOption]; + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + [super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }); } #pragma mark - ASDataControllerDelegate @@ -523,7 +649,7 @@ static BOOL _isInterceptedSelector(SEL sel) } } -- (NSUInteger)dataController:(ASDataController *)dataControllre rowsInSection:(NSUInteger)section +- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section { return [_asyncDataSource tableView:self numberOfRowsInSection:section]; } diff --git a/AsyncDisplayKit/ASTextNode.h b/AsyncDisplayKit/ASTextNode.h index 11fe337de0..fc520c1232 100644 --- a/AsyncDisplayKit/ASTextNode.h +++ b/AsyncDisplayKit/ASTextNode.h @@ -76,6 +76,8 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { */ @property (nonatomic, readonly, assign) NSUInteger lineCount; +@property (nonatomic, strong) NSArray *exclusionPaths; + #pragma mark - Placeholders /** @@ -192,6 +194,11 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { */ @property (nonatomic, assign) BOOL longPressCancelsTouches; +/** + @abstract if YES will not intercept touches for non-link areas of the text. Default is NO. + */ +@property (nonatomic, assign) BOOL passthroughNonlinkTouches; + @end diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index e631f9d28b..0206040c07 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -87,6 +87,8 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) CGFloat _shadowOpacity; CGFloat _shadowRadius; + NSArray *_exclusionPaths; + NSAttributedString *_composedTruncationString; NSString *_highlightedLinkAttributeName; @@ -224,12 +226,12 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) [self _invalidateRenderer]; } -- (void)clearRendering +- (void)clearContents { // We discard the backing store and renderer to prevent the very large // memory overhead of maintaining these for all text nodes. They can be // regenerated when layout is necessary. - [super clearRendering]; // ASDisplayNode will set layer.contents = nil + [super clearContents]; // ASDisplayNode will set layer.contents = nil [self _invalidateRenderer]; } @@ -281,6 +283,7 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) truncationString:_composedTruncationString truncationMode:_truncationMode maximumLineCount:_maximumLineCount + exclusionPaths:_exclusionPaths constrainedSize:constrainedSize]; } return _renderer; @@ -334,8 +337,8 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) // We need an entirely new renderer [self _invalidateRenderer]; - // Tell the display node superclasses that the cached sizes are incorrect now - [self invalidateCalculatedSize]; + // Tell the display node superclasses that the cached layout is incorrect now + [self invalidateCalculatedLayout]; [self setNeedsDisplay]; @@ -349,6 +352,23 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) } } +#pragma mark - Text Layout + +- (void)setExclusionPaths:(NSArray *)exclusionPaths +{ + if ((_exclusionPaths == nil && exclusionPaths != nil) || (![_exclusionPaths isEqualToArray:exclusionPaths])) { + _exclusionPaths = exclusionPaths; + [self _invalidateRenderer]; + [self invalidateCalculatedLayout]; + [self setNeedsDisplay]; + } +} + +- (NSArray *)exclusionPaths +{ + return _exclusionPaths; +} + #pragma mark - Drawing + (void)drawRect:(CGRect)bounds withParameters:(ASTextNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing @@ -749,6 +769,33 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) #pragma mark - Touch Handling +-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event +{ + if (!_passthroughNonlinkTouches) { + return [super pointInside:point withEvent:event]; + } + + NSRange range = NSMakeRange(0, 0); + NSString *linkAttributeName = nil; + BOOL inAdditionalTruncationMessage = NO; + + id linkAttributeValue = [self _linkAttributeValueAtPoint:point + attributeName:&linkAttributeName + range:&range + inAdditionalTruncationMessage:&inAdditionalTruncationMessage]; + + NSUInteger lastCharIndex = NSIntegerMax; + BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); + + if (inAdditionalTruncationMessage) { + return YES; + } else if (range.length && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { + return YES; + } else { + return NO; + } +} + - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 2fe59c2a16..46b3677f1b 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -24,3 +24,16 @@ #import #import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.h b/AsyncDisplayKit/Details/ASAbstractLayoutController.h new file mode 100644 index 0000000000..45b92ece00 --- /dev/null +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +@interface ASAbstractLayoutController : NSObject + +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; + +- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; + +@property (nonatomic, assign) ASRangeTuningParameters tuningParameters ASDISPLAYNODE_DEPRECATED; + +@end diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm new file mode 100644 index 0000000000..28d6ceb04f --- /dev/null +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -0,0 +1,95 @@ +/* Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "ASAbstractLayoutController.h" + +#include + +#import "ASAssert.h" + +@interface ASAbstractLayoutController () { + std::vector _tuningParameters; +} +@end + +@implementation ASAbstractLayoutController + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + _tuningParameters = std::vector(ASLayoutRangeTypeCount); + _tuningParameters[ASLayoutRangeTypePreload] = { + .leadingBufferScreenfuls = 3, + .trailingBufferScreenfuls = 2 + }; + _tuningParameters[ASLayoutRangeTypeRender] = { + .leadingBufferScreenfuls = 2, + .trailingBufferScreenfuls = 1 + }; + + return self; +} + +#pragma mark - Tuning Parameters + +- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType +{ + ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters"); + return _tuningParameters[rangeType]; +} + +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType +{ + ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters"); + _tuningParameters[rangeType] = tuningParameters; +} + +// Support for the deprecated tuningParameters property +- (ASRangeTuningParameters)tuningParameters +{ + return [self tuningParametersForRangeType:ASLayoutRangeTypeRender]; +} + +// Support for the deprecated tuningParameters property +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters +{ + [self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender]; +} + +#pragma mark - Index Path Range Support + +// Support for deprecated method +- (BOOL)shouldUpdateForVisibleIndexPath:(NSArray *)indexPaths viewportSize:(CGSize)viewportSize +{ + return [self shouldUpdateForVisibleIndexPaths:indexPaths viewportSize:viewportSize rangeType:ASLayoutRangeTypeRender]; +} + +// Support for the deprecated method +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize +{ + return [self indexPathsForScrolling:scrollDirection viewportSize:viewportSize rangeType:ASLayoutRangeTypeRender]; +} + +#pragma mark - Abstract + +- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType +{ + ASDisplayNodeAssertNotSupported(); + return NO; +} + +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType +{ + ASDisplayNodeAssertNotSupported(); + return nil; +} + +@end diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm index 1d082bcecc..b1693c3017 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm @@ -20,15 +20,21 @@ /** * Collection of properties associated with a download request. */ + +typedef void (^ASBasicImageDownloaderContextProgressBlock)(CGFloat); +typedef void (^ASBasicImageDownloaderContextCompletionBlock)(CGImageRef, NSError *); + +NSString * const kASBasicImageDownloaderContextCallbackQueue = @"kASBasicImageDownloaderContextCallbackQueue"; +NSString * const kASBasicImageDownloaderContextProgressBlock = @"kASBasicImageDownloaderContextProgressBlock"; +NSString * const kASBasicImageDownloaderContextCompletionBlock = @"kASBasicImageDownloaderContextCompletionBlock"; + @interface ASBasicImageDownloaderContext () { BOOL _invalid; ASDN::RecursiveMutex _propertyLock; } -@property (nonatomic, strong) dispatch_queue_t callbackQueue; -@property (nonatomic, copy) void (^downloadProgressBlock)(CGFloat); -@property (nonatomic, copy) void (^completionBlock)(CGImageRef, NSError *); +@property (nonatomic, strong) NSMutableArray *callbackDatas; @end @@ -63,6 +69,7 @@ static ASDN::RecursiveMutex currentRequestsLock; { if (self = [super init]) { _URL = URL; + _callbackDatas = [NSMutableArray array]; } return self; } @@ -87,6 +94,77 @@ static ASDN::RecursiveMutex currentRequestsLock; return _invalid; } +- (void)addCallbackData:(NSDictionary *)callbackData +{ + ASDN::MutexLocker l(_propertyLock); + [self.callbackDatas addObject:callbackData]; +} + +- (void)performProgressBlocks:(CGFloat)progress +{ + ASDN::MutexLocker l(_propertyLock); + for (NSDictionary *callbackData in self.callbackDatas) { + ASBasicImageDownloaderContextProgressBlock progressBlock = callbackData[kASBasicImageDownloaderContextProgressBlock]; + dispatch_queue_t callbackQueue = callbackData[kASBasicImageDownloaderContextCallbackQueue]; + + if (progressBlock) { + dispatch_async(callbackQueue, ^{ + progressBlock(progress); + }); + } + } +} + +- (void)completeWithImage:(UIImage *)image error:(NSError *)error +{ + ASDN::MutexLocker l(_propertyLock); + for (NSDictionary *callbackData in self.callbackDatas) { + ASBasicImageDownloaderContextCompletionBlock completionBlock = callbackData[kASBasicImageDownloaderContextCompletionBlock]; + dispatch_queue_t callbackQueue = callbackData[kASBasicImageDownloaderContextCallbackQueue]; + + if (completionBlock) { + dispatch_async(callbackQueue, ^{ + completionBlock(image.CGImage, error); + }); + } + } + + self.sessionTask = nil; + [self.callbackDatas removeAllObjects]; +} + +- (NSURLSessionTask *)createSessionTaskIfNecessaryWithBlock:(NSURLSessionTask *(^)())creationBlock { + { + ASDN::MutexLocker l(_propertyLock); + + if (self.isCancelled) { + return nil; + } + + if (self.sessionTask && (self.sessionTask.state == NSURLSessionTaskStateRunning)) { + return nil; + } + } + + NSURLSessionTask *newTask = creationBlock(); + + { + ASDN::MutexLocker l(_propertyLock); + + if (self.isCancelled) { + return nil; + } + + if (self.sessionTask && (self.sessionTask.state == NSURLSessionTaskStateRunning)) { + return nil; + } + + self.sessionTask = newTask; + + return self.sessionTask; + } +} + @end @@ -150,30 +228,29 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext // NSURLSessionDownloadTask will do file I/O to create a temp directory. If called on the main thread this will // cause significant performance issues. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - // the downloader may have been invalidated in the time it takes to async dispatch this block - if ([context isCancelled]) { - return; - } - - // create download task - NSURLSessionDownloadTask *task = [_session downloadTaskWithURL:URL]; - - // since creating the task does disk I/O, we should check if it has been invalidated - if ([context isCancelled]) { - return; - } - // associate metadata with it - context.callbackQueue = callbackQueue ?: dispatch_get_main_queue(); - context.downloadProgressBlock = downloadProgressBlock; - context.completionBlock = completion; - context.sessionTask = task; - task.originalRequest.asyncdisplaykit_context = context; + NSMutableDictionary *callbackData = [NSMutableDictionary dictionary]; + callbackData[kASBasicImageDownloaderContextCallbackQueue] = callbackQueue ?: dispatch_get_main_queue(); - // start downloading - [task resume]; + if (downloadProgressBlock) { + callbackData[kASBasicImageDownloaderContextProgressBlock] = [downloadProgressBlock copy]; + } - context.sessionTask = task; + if (completion) { + callbackData[kASBasicImageDownloaderContextCompletionBlock] = [completion copy]; + } + + [context addCallbackData:[NSDictionary dictionaryWithDictionary:callbackData]]; + + // Create new task if necessary + NSURLSessionDownloadTask *task = (NSURLSessionDownloadTask *)[context createSessionTaskIfNecessaryWithBlock:^(){return [_session downloadTaskWithURL:URL];}]; + + if (task) { + task.originalRequest.asyncdisplaykit_context = context; + + // start downloading + [task resume]; + } }); return context; @@ -200,9 +277,7 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { ASBasicImageDownloaderContext *context = downloadTask.originalRequest.asyncdisplaykit_context; - if (context.downloadProgressBlock) { - context.downloadProgressBlock((CGFloat)totalBytesWritten / (CGFloat)totalBytesExpectedToWrite); - } + [context performProgressBlocks:(CGFloat)totalBytesWritten / (CGFloat)totalBytesExpectedToWrite]; } // invoked if the download succeeded with no error @@ -214,12 +289,9 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext return; } - UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]]; - - if (context.completionBlock) { - dispatch_async(context.callbackQueue, ^{ - context.completionBlock(image.CGImage, nil); - }); + if (context) { + UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]]; + [context completeWithImage:image error:nil]; } } @@ -229,9 +301,7 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext { ASBasicImageDownloaderContext *context = task.originalRequest.asyncdisplaykit_context; if (context && error) { - dispatch_async(context.callbackQueue, ^{ - context.completionBlock(NULL, error); - }); + [context completeWithImage:nil error:error]; } } diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h new file mode 100644 index 0000000000..9aa25db0a9 --- /dev/null +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +@class ASCollectionView; + +@interface ASCollectionViewLayoutController : ASAbstractLayoutController + +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView; + +@end diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm new file mode 100644 index 0000000000..8655102738 --- /dev/null +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm @@ -0,0 +1,163 @@ +/* Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "ASCollectionViewLayoutController.h" + +#include + +#import "ASAssert.h" +#import "ASCollectionView.h" +#import "CGRect+ASConvenience.h" + +struct ASDirectionalScreenfulBuffer { + CGFloat positiveDirection; // Positive relative to iOS Core Animation layer coordinate space. + CGFloat negativeDirection; +}; +typedef struct ASDirectionalScreenfulBuffer ASDirectionalScreenfulBuffer; + +ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection, + ASRangeTuningParameters rangeTuningParameters) +{ + ASDirectionalScreenfulBuffer horizontalBuffer = {0, 0}; + BOOL movingRight = ASScrollDirectionContainsRight(scrollDirection); + horizontalBuffer.positiveDirection = movingRight ? rangeTuningParameters.leadingBufferScreenfuls : + rangeTuningParameters.trailingBufferScreenfuls; + horizontalBuffer.negativeDirection = movingRight ? rangeTuningParameters.trailingBufferScreenfuls : + rangeTuningParameters.leadingBufferScreenfuls; + return horizontalBuffer; +} + +ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection, + ASRangeTuningParameters rangeTuningParameters) +{ + ASDirectionalScreenfulBuffer verticalBuffer = {0, 0}; + BOOL movingDown = ASScrollDirectionContainsDown(scrollDirection); + verticalBuffer.positiveDirection = movingDown ? rangeTuningParameters.leadingBufferScreenfuls : + rangeTuningParameters.trailingBufferScreenfuls; + verticalBuffer.negativeDirection = movingDown ? rangeTuningParameters.trailingBufferScreenfuls : + rangeTuningParameters.leadingBufferScreenfuls; + return verticalBuffer; +} + +struct ASRangeGeometry { + CGRect rangeBounds; + CGRect updateBounds; +}; +typedef struct ASRangeGeometry ASRangeGeometry; + + +#pragma mark - +#pragma mark ASCollectionViewLayoutController + +@interface ASCollectionViewLayoutController () +{ + UIScrollView * __weak _scrollView; + UICollectionViewLayout * __strong _collectionViewLayout; + std::vector _updateRangeBoundsIndexedByRangeType; + ASScrollDirection _scrollableDirections; +} +@end + +@implementation ASCollectionViewLayoutController + +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView +{ + if (!(self = [super init])) { + return nil; + } + + _scrollableDirections = [collectionView scrollableDirections]; + _scrollView = collectionView; + _collectionViewLayout = [collectionView collectionViewLayout]; + _updateRangeBoundsIndexedByRangeType = std::vector(ASLayoutRangeTypeCount); + return self; +} + +#pragma mark - +#pragma mark Index Paths in Range + +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection + viewportSize:(CGSize)viewportSize + rangeType:(ASLayoutRangeType)rangeType +{ + ASRangeGeometry rangeGeometry = [self rangeGeometryWithScrollDirection:scrollDirection + rangeTuningParameters:[self tuningParametersForRangeType:rangeType]]; + _updateRangeBoundsIndexedByRangeType[rangeType] = rangeGeometry.updateBounds; + return [self indexPathsForItemsWithinRangeBounds:rangeGeometry.rangeBounds]; +} + +- (ASRangeGeometry)rangeGeometryWithScrollDirection:(ASScrollDirection)scrollDirection + rangeTuningParameters:(ASRangeTuningParameters)rangeTuningParameters +{ + CGRect rangeBounds = _scrollView.bounds; + CGRect updateBounds = _scrollView.bounds; + + BOOL canScrollHorizontally = ASScrollDirectionContainsHorizontalDirection(_scrollableDirections); + if (canScrollHorizontally) { + ASDirectionalScreenfulBuffer horizontalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, + rangeTuningParameters); + rangeBounds = asdk_CGRectExpandHorizontally(rangeBounds, + horizontalBuffer.negativeDirection, + horizontalBuffer.positiveDirection); + // Update bounds is at most 95% of the next/previous screenful and at least half of tuning parameter value. + updateBounds = asdk_CGRectExpandHorizontally(updateBounds, + MIN(horizontalBuffer.negativeDirection * 0.5, 0.95), + MIN(horizontalBuffer.positiveDirection * 0.5, 0.95)); + } + + BOOL canScrollVertically = ASScrollDirectionContainsVerticalDirection(_scrollableDirections); + if (canScrollVertically) { + ASDirectionalScreenfulBuffer verticalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, + rangeTuningParameters); + rangeBounds = asdk_CGRectExpandVertically(rangeBounds, + verticalBuffer.negativeDirection, + verticalBuffer.positiveDirection); + // Update bounds is at most 95% of the next/previous screenful and at least half of tuning parameter value. + updateBounds = asdk_CGRectExpandVertically(updateBounds, + MIN(verticalBuffer.negativeDirection * 0.5, 0.95), + MIN(verticalBuffer.positiveDirection * 0.5, 0.95)); + } + + return {rangeBounds, updateBounds}; +} + +- (NSSet *)indexPathsForItemsWithinRangeBounds:(CGRect)rangeBounds +{ + NSMutableSet *indexPathSet = [[NSMutableSet alloc] init]; + NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds]; + for (UICollectionViewLayoutAttributes *la in layoutAttributes) { + [indexPathSet addObject:la.indexPath]; + } + return indexPathSet; +} + +#pragma mark - +#pragma mark Should Update Range + +- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths + viewportSize:(CGSize)viewportSize + rangeType:(ASLayoutRangeType)rangeType +{ + CGRect updateRangeBounds = _updateRangeBoundsIndexedByRangeType[rangeType]; + if (CGRectIsEmpty(updateRangeBounds)) { + return YES; + } + + CGRect currentBounds = _scrollView.bounds; + if (CGRectIsEmpty(currentBounds)) { + currentBounds = CGRectMake(0, 0, viewportSize.width, viewportSize.height); + } + + if (CGRectContainsRect(updateRangeBounds, currentBounds)) { + return NO; + } else { + return YES; + } +} + +@end diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 996d84427f..08dfd4fbf2 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -8,7 +8,7 @@ #import #import - +#import "ASFlowLayoutController.h" @class ASCellNode; @class ASDataController; @@ -70,26 +70,22 @@ typedef NSUInteger ASDataControllerAnimationOptions; /** Called for insertion of elements. */ -- (void)dataController:(ASDataController *)dataController willInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption; -- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** Called for deletion of elements. */ -- (void)dataController:(ASDataController *)dataController willDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption; -- (void)dataController:(ASDataController *)dataController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)dataController:(ASDataController *)dataController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** Called for insertion of sections. */ -- (void)dataController:(ASDataController *)dataController willInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption; -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** Called for deletion of sections. */ -- (void)dataController:(ASDataController *)dataController willDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption; -- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; @end @@ -101,7 +97,8 @@ typedef NSUInteger ASDataControllerAnimationOptions; * will be updated asynchronously. The dataSource must be updated to reflect the changes before these methods has been called. * For each data updatin, the corresponding methods in delegate will be called. */ -@interface ASDataController : ASDealloc2MainObject +@protocol ASFlowLayoutControllerDataSource; +@interface ASDataController : ASDealloc2MainObject /** Data source for fetching data info. @@ -117,19 +114,23 @@ typedef NSUInteger ASDataControllerAnimationOptions; * Designated iniailizer. * * @param asyncDataFetchingEnabled Enable the data fetching in async mode. - + * * @discussion If enabled, we will fetch data through `dataController:nodeAtIndexPath:` and `dataController:rowsInSection:` in background thread. * Otherwise, the methods will be invoked synchronically in calling thread. Enabling data fetching in async mode could avoid blocking main thread - * while allocating cell on main thread, which is frequently reported issue for handing large scale data. On another hand, the application code + * while allocating cell on main thread, which is frequently reported issue for handling large scale data. On another hand, the application code * will take the responsibility to avoid data inconsistence. Specifically, we will lock the data source through `dataControllerLockDataSource`, * and unlock it by `dataControllerUnlockDataSource` after the data fetching. The application should not update the data source while * the data source is locked. */ - (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; -/** @name Initial loading */ +/** @name Initial loading + * + * @discussion This method allows choosing an animation style for the first load of content. It is typically used just once, + * for example in viewWillAppear:, to specify an animation option for the information already present in the asyncDataSource. + */ -- (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** @name Data Updating */ @@ -139,23 +140,23 @@ typedef NSUInteger ASDataControllerAnimationOptions; - (void)endUpdatesWithCompletion:(void (^)(BOOL))completion; -- (void)insertSections:(NSIndexSet *)sections withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)deleteSections:(NSIndexSet *)sections withAnimationOption:(ASDataControllerAnimationOptions)animationOption;; +- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)reloadSections:(NSIndexSet *)sections withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOption:(ASDataControllerAnimationOptions)animationOption;; +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOption:(ASDataControllerAnimationOptions)animationOption;; +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)reloadDataWithAnimationOption:(ASDataControllerAnimationOptions)animationOption;; +- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion; /** @name Data Querying */ @@ -167,4 +168,6 @@ typedef NSUInteger ASDataControllerAnimationOptions; - (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths; +- (NSArray *)completedNodes; // This provides efficient access to the entire _completedNodes multidimensional array. + @end diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index ed70155727..c3d1a916c6 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -16,58 +16,22 @@ #import "ASMultidimensionalArrayUtils.h" #import "ASDisplayNodeInternal.h" -#define INSERT_NODES(multidimensionalArray, indexPath, elements, animationOption) \ -{ \ - if ([_delegate respondsToSelector:@selector(dataController:willInsertNodes:atIndexPaths:withAnimationOption:)]) { \ - [_delegate dataController:self willInsertNodes:elements atIndexPaths:indexPath withAnimationOption:animationOption]; \ - } \ - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(multidimensionalArray, indexPath, elements); \ - if ([_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOption:)]) { \ - [_delegate dataController:self didInsertNodes:elements atIndexPaths:indexPath withAnimationOption:animationOption]; \ - } \ -} - -#define DELETE_NODES(multidimensionalArray, indexPath, animationOption) \ -{ \ - if ([_delegate respondsToSelector:@selector(dataController:willDeleteNodesAtIndexPaths:withAnimationOption:)]) { \ - [_delegate dataController:self willDeleteNodesAtIndexPaths:indexPath withAnimationOption:animationOption]; \ - } \ - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(multidimensionalArray, indexPath); \ - if ([_delegate respondsToSelector:@selector(dataController:didDeleteNodesAtIndexPaths:withAnimationOption:)]) { \ - [_delegate dataController:self didDeleteNodesAtIndexPaths:indexPath withAnimationOption:animationOption]; \ - } \ -} - -#define INSERT_SECTIONS(multidimensionalArray, indexSet, sections, animationOption) \ -{ \ - if ([_delegate respondsToSelector:@selector(dataController:willInsertSections:atIndexSet:withAnimationOption:)]) { \ - [_delegate dataController:self willInsertSections:sections atIndexSet:indexSet withAnimationOption:animationOption]; \ - } \ - [multidimensionalArray insertObjects:sections atIndexes:indexSet]; \ - if ([_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOption:)]) { \ - [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOption:animationOption]; \ - } \ -} - -#define DELETE_SECTIONS(multidimensionalArray, indexSet, animationOption) \ -{ \ - if ([_delegate respondsToSelector:@selector(dataController:willDeleteSectionsAtIndexSet:withAnimationOption:)]) { \ - [_delegate dataController:self willDeleteSectionsAtIndexSet:indexSet withAnimationOption:animationOption]; \ - } \ - [multidimensionalArray removeObjectsAtIndexes:indexSet]; \ - if ([_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOption:)]) { \ - [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOption:animationOption]; \ - } \ -} - const static NSUInteger kASDataControllerSizingCountPerProcessor = 5; static void *kASSizingQueueContext = &kASSizingQueueContext; @interface ASDataController () { - NSMutableArray *_nodes; - NSMutableArray *_pendingBlocks; + NSMutableArray *_completedNodes; // Main thread only. External data access can immediately query this. + NSMutableArray *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. + + NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking. + NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. + BOOL _asyncDataFetchingEnabled; + BOOL _delegateDidInsertNodes; + BOOL _delegateDidDeleteNodes; + BOOL _delegateDidInsertSections; + BOOL _delegateDidDeleteSections; } @property (atomic, assign) NSUInteger batchUpdateCounter; @@ -76,20 +40,46 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; @implementation ASDataController -- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled { - if (self = [super init]) { - _nodes = [NSMutableArray array]; - _pendingBlocks = [NSMutableArray array]; - _batchUpdateCounter = 0; - _asyncDataFetchingEnabled = asyncDataFetchingEnabled; - } +#pragma mark - Lifecycle +- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled +{ + if (!(self = [super init])) { + return nil; + } + + _completedNodes = [NSMutableArray array]; + _editingNodes = [NSMutableArray array]; + + _pendingEditCommandBlocks = [NSMutableArray array]; + + _editingTransactionQueue = [[NSOperationQueue alloc] init]; + _editingTransactionQueue.maxConcurrentOperationCount = 1; // Serial queue + _editingTransactionQueue.name = @"org.AsyncDisplayKit.ASDataController.editingTransactionQueue"; + + _batchUpdateCounter = 0; + _asyncDataFetchingEnabled = asyncDataFetchingEnabled; + return self; } -#pragma mark - Utils +- (void)setDelegate:(id)delegate +{ + if (_delegate == delegate) { + return; + } + + _delegate = delegate; + + // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later. + _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)]; + _delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodesAtIndexPaths:withAnimationOptions:)]; + _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)]; + _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; +} -+ (NSUInteger)parallelProcessorCount { ++ (NSUInteger)parallelProcessorCount +{ static NSUInteger parallelProcessorCount; static dispatch_once_t onceToken; @@ -100,44 +90,187 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; return parallelProcessorCount; } -+ (dispatch_queue_t)sizingQueue +#pragma mark - Cell Layout + +- (void)_layoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - static dispatch_queue_t sizingQueue = NULL; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sizingQueue = dispatch_queue_create("com.facebook.AsyncDisplayKit.ASDataController.sizingQueue", DISPATCH_QUEUE_SERIAL); - dispatch_queue_set_specific(sizingQueue, kASSizingQueueContext, kASSizingQueueContext, NULL); - dispatch_set_target_queue(sizingQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); - }); - - return sizingQueue; + ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"Cell node layout must be initiated from edit transaction queue"); + + if (!nodes.count) { + return; + } + + dispatch_group_t layoutGroup = dispatch_group_create(); + + for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { + NSArray *subIndexPaths = [indexPaths subarrayWithRange:NSMakeRange(j, MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j))]; + + // TODO: The current implementation does not make use of different constrained sizes per node. + // There should be a fast-path that avoids all of this object creation. + NSMutableArray *nodeBoundSizes = [[NSMutableArray alloc] initWithCapacity:kASDataControllerSizingCountPerProcessor]; + [subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { + [nodeBoundSizes addObject:[NSValue valueWithCGSize:[_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]]]; + }]; + + dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { + ASCellNode *node = nodes[j + idx]; + [node measure:[nodeBoundSizes[idx] CGSizeValue]]; + node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); + }]; + }); + } + + // Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated. + dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); + + // Insert finished nodes into data storage + [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; } -+ (BOOL)isSizingQueue { - return kASSizingQueueContext == dispatch_get_specific(kASSizingQueueContext); +- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; + + // Processing in batches + for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { + NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); + NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; + NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; + + [self _layoutNodes:batchedNodes atIndexPaths:batchedIndexPaths withAnimationOptions:animationOptions]; + } } -- (void)asyncUpdateDataWithBlock:(dispatch_block_t)block { - dispatch_async(dispatch_get_main_queue(), ^{ - if (_batchUpdateCounter) { - [_pendingBlocks addObject:block]; - } else { - block(); - } +#pragma mark - Internal Data Querying + Editing + +- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + if (indexPaths.count == 0) + return; + ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(_editingNodes, indexPaths, nodes); + + // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. + NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_editingNodes); + + ASDisplayNodePerformBlockOnMainThread(^{ + _completedNodes = completedNodes; + if (_delegateDidInsertNodes) + [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }); } -- (void)syncUpdateDataWithBlock:(dispatch_block_t)block { - dispatch_sync(dispatch_get_main_queue(), ^{ - if (_batchUpdateCounter) { - [_pendingBlocks addObject:block]; - } else { - block(); - } +- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + if (indexPaths.count == 0) + return; + ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, indexPaths); + + ASDisplayNodePerformBlockOnMainThread(^{ + ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, indexPaths); + if (_delegateDidDeleteNodes) + [_delegate dataController:self didDeleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; }); } -- (void)performDataFetchingWithBlock:(dispatch_block_t)block { +- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + if (indexSet.count == 0) + return; + [_editingNodes insertObjects:sections atIndexes:indexSet]; + + // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. + NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections); + + ASDisplayNodePerformBlockOnMainThread(^{ + [_completedNodes insertObjects:sectionsForCompleted atIndexes:indexSet]; + if (_delegateDidInsertSections) + [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; + }); +} + +- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + if (indexSet.count == 0) + return; + [_editingNodes removeObjectsAtIndexes:indexSet]; + ASDisplayNodePerformBlockOnMainThread(^{ + [_completedNodes removeObjectsAtIndexes:indexSet]; + if (_delegateDidDeleteSections) + [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + }); +} + +#pragma mark - Initial Load & Full Reload (External API) + +- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [self accessDataSourceWithBlock:^{ + NSMutableArray *indexPaths = [NSMutableArray array]; + NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; + + // insert sections + [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0]; + + for (NSUInteger i = 0; i < sectionNum; i++) { + NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i]; + + NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; + for (NSUInteger j = 0; j < rowNum; j++) { + [indexPaths addObject:[indexPath indexPathByAddingIndex:j]]; + } + } + + // insert elements + [self insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; +} + +- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion +{ + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + + [self accessDataSourceWithBlock:^{ + NSUInteger sectionCount = [_dataSource dataControllerNumberOfSections:self]; + NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedIndexPaths = [NSMutableArray array]; + [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + + [_editingTransactionQueue addOperationWithBlock:^{ + // Remove everything that existed before the reload, now that we're ready to insert replacements + NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + + NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _editingNodes.count)]; + [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + + // Insert each section + NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; + for (int i = 0; i < sectionCount; i++) { + [sections addObject:[[NSMutableArray alloc] init]]; + } + + [self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions]; + + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + + if (completion) { + dispatch_async(dispatch_get_main_queue(), completion); + } + }]; + }]; + }]; +} + +#pragma mark - Data Source Access (Calling _dataSource) + +- (void)accessDataSourceWithBlock:(dispatch_block_t)block +{ if (_asyncDataFetchingEnabled) { [_dataSource dataControllerLockDataSource]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ @@ -151,157 +284,159 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } } -#pragma mark - Initial Data Loading - -- (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption { - [self performDataFetchingWithBlock:^{ - NSMutableArray *indexPaths = [NSMutableArray array]; - NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; - - // insert sections - [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOption:0]; - - for (NSUInteger i = 0; i < sectionNum; i++) { - NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i]; - - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; - for (NSUInteger j = 0; j < rowNum; j++) { - [indexPaths addObject:[indexPath indexPathByAddingIndex:j]]; - } +- (void)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet mutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths +{ + [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx]; + + NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; + for (NSUInteger i = 0; i < rowNum; i++) { + NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; + [indexPaths addObject:indexPath]; + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; } - - // insert elements - [self insertRowsAtIndexPaths:indexPaths withAnimationOption:animationOption]; - }]; } -#pragma mark - Data Update +- (void)_populateFromEntireDataSourceWithMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths +{ + NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; + for (NSUInteger i = 0; i < sectionNum; i++) { + NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i]; + + NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; + for (NSUInteger j = 0; j < rowNum; j++) { + NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; + [indexPaths addObject:indexPath]; + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + } + } +} + + +#pragma mark - Batching (External API) - (void)beginUpdates { - dispatch_async([[self class] sizingQueue], ^{ - [self asyncUpdateDataWithBlock:^{ - _batchUpdateCounter++; - }]; - }); + // Begin queuing up edit calls that happen on the main thread. + // This will prevent further operations from being scheduled on _editingTransactionQueue. + // It's fine if there is an in-flight operation on _editingTransactionQueue, + // as once the command queue is unpaused, each edit command will wait for the _editingTransactionQueue to be flushed. + _batchUpdateCounter++; } -- (void)endUpdates { +- (void)endUpdates +{ [self endUpdatesWithCompletion:NULL]; } - (void)endUpdatesWithCompletion:(void (^)(BOOL))completion { - dispatch_async([[self class] sizingQueue], ^{ - dispatch_async(dispatch_get_main_queue(), ^{ - _batchUpdateCounter--; + _batchUpdateCounter--; - if (!_batchUpdateCounter) { - [_delegate dataControllerBeginUpdates:self]; - [_pendingBlocks enumerateObjectsUsingBlock:^(dispatch_block_t block, NSUInteger idx, BOOL *stop) { - block(); - }]; - [_pendingBlocks removeAllObjects]; - [_delegate dataControllerEndUpdates:self completion:completion]; - } - }); - }); + if (_batchUpdateCounter == 0) { + [_delegate dataControllerBeginUpdates:self]; + // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. + // Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction. + [_pendingEditCommandBlocks enumerateObjectsUsingBlock:^(dispatch_block_t block, NSUInteger idx, BOOL *stop) { + block(); + }]; + [_pendingEditCommandBlocks removeAllObjects]; + + [_delegate dataControllerEndUpdates:self completion:completion]; + } } -- (void)insertSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)performEditCommandWithBlock:(void (^)(void))block { - [self performDataFetchingWithBlock:^{ - __block int nodeTotalCnt = 0; - NSMutableArray *nodeCounts = [NSMutableArray arrayWithCapacity:indexSet.count]; - [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - NSUInteger cnt = [_dataSource dataController:self rowsInSection:idx]; - [nodeCounts addObject:@(cnt)]; - nodeTotalCnt += cnt; - }]; + // This method needs to block the thread and synchronously perform the operation if we are not + // queuing commands for begin/endUpdates. If we are queuing, it needs to return immediately. + if (_batchUpdateCounter == 0) { + block(); + } else { + [_pendingEditCommandBlocks addObject:block]; + } +} - NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:nodeTotalCnt]; - NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodeTotalCnt]; +#pragma mark - Section Editing (External API) - __block NSUInteger idx = 0; - [indexSet enumerateIndexesUsingBlock:^(NSUInteger sectionIdx, BOOL *stop) { - NSUInteger cnt = [nodeCounts[idx++] unsignedIntegerValue]; - - for (int i = 0; i < cnt; i++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIdx]; - [indexPaths addObject:indexPath]; - - ASCellNode *node = [_dataSource dataController:self nodeAtIndexPath:indexPath]; - [nodes addObject:node]; - } - }]; - - dispatch_async([[self class] sizingQueue], ^{ - [self syncUpdateDataWithBlock:^{ +- (void)insertSections:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + + [self accessDataSourceWithBlock:^{ + NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedIndexPaths = [NSMutableArray array]; + [self _populateFromDataSourceWithSectionIndexSet:indexSet mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + + [_editingTransactionQueue addOperationWithBlock:^{ NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:indexSet.count]; for (NSUInteger i = 0; i < indexSet.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } - INSERT_SECTIONS(_nodes , indexSet, sectionArray, animationOption); + + [self _insertSections:sectionArray atIndexSet:indexSet withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; }]; - - [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; - }); + }]; }]; } -- (void)deleteSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)deleteSections:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - dispatch_async([[self class] sizingQueue], ^{ - [self asyncUpdateDataWithBlock:^{ + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + + [_editingTransactionQueue addOperationWithBlock:^{ // remove elements - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_nodes, indexSet); + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, indexSet); - DELETE_NODES(_nodes, indexPaths, animationOption); - DELETE_SECTIONS(_nodes, indexSet, animationOption); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }]; - }); -} - -- (void)reloadSections:(NSIndexSet *)sections withAnimationOption:(ASDataControllerAnimationOptions)animationOption -{ - [self performDataFetchingWithBlock:^{ - // We need to keep data query on data source in the calling thread. - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init]; - NSMutableArray *updatedNodes = [[NSMutableArray alloc] init]; - - [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx]; - - NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; - for (NSUInteger i = 0; i < rowNum; i++) { - NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; - [updatedIndexPaths addObject:indexPath]; - [updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; - } - }]; - - dispatch_async([ASDataController sizingQueue], ^{ - [self syncUpdateDataWithBlock:^{ - // remove elements - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_nodes, sections); - DELETE_NODES(_nodes, indexPaths, animationOption); - }]; - - // reinsert the elements - [self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption]; - }); }]; } -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - dispatch_async([ASDataController sizingQueue], ^{ - [self asyncUpdateDataWithBlock:^{ + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + + [self accessDataSourceWithBlock:^{ + NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedIndexPaths = [NSMutableArray array]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + + // Dispatch to sizing queue in order to guarantee that any in-progress sizing operations from prior edits have completed. + // For example, if an initial -reloadData call is quickly followed by -reloadSections, sizing the initial set may not be done + // at this time. Thus _editingNodes could be empty and crash in ASIndexPathsForMultidimensional[...] + + [_editingTransactionQueue addOperationWithBlock:^{ + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, sections); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + + // reinsert the elements + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + }]; + }]; + }]; +} + +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + + [_editingTransactionQueue addOperationWithBlock:^{ // remove elements - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_nodes, [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_nodes, indexPaths); - DELETE_NODES(_nodes, indexPaths, animationOption); + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, [NSIndexSet indexSetWithIndex:section]); + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, indexPaths); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; // update the section of indexpaths NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; @@ -311,201 +446,137 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; // Don't re-calculate size for moving - INSERT_NODES(_nodes, updatedIndexPaths, nodes, animationOption); + [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; }]; - }); + }]; } -- (void)_insertNodes:(NSArray *)nodes - atIndexPaths:(NSArray *)indexPaths - withAnimationOption:(ASDataControllerAnimationOptions)animationOption +#pragma mark - Row Editing (External API) + +- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - if (!nodes.count) { - return; - } - - dispatch_group_t layoutGroup = dispatch_group_create(); - - for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { - NSArray *subIndexPaths = [indexPaths subarrayWithRange:NSMakeRange(j, MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j))]; - - NSMutableArray *nodeBoundSizes = [[NSMutableArray alloc] initWithCapacity:kASDataControllerSizingCountPerProcessor]; - [subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - [nodeBoundSizes addObject:[NSValue valueWithCGSize:[_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]]]; - }]; - - dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - ASCellNode *node = nodes[j + idx]; - [node measure:[nodeBoundSizes[idx] CGSizeValue]]; - node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + + [self accessDataSourceWithBlock:^{ + // sort indexPath to avoid messing up the index when inserting in several batches + NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; + NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + for (NSUInteger i = 0; i < sortedIndexPaths.count; i++) { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]]; + } + + [_editingTransactionQueue addOperationWithBlock:^{ + [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; - }); - } - - dispatch_block_t block = ^{ - dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); - - [self asyncUpdateDataWithBlock:^{ - // updating the cells - INSERT_NODES(_nodes, indexPaths, nodes, animationOption); }]; - }; - - if ([ASDataController isSizingQueue]) { - block(); - } else { - dispatch_async([ASDataController sizingQueue], block); - } + }]; } -- (void)_batchInsertNodes:(NSArray *)nodes - atIndexPaths:(NSArray *)indexPaths - withAnimationOptions:(ASDataControllerAnimationOptions)animationOption +- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; - - // Processing in batches - for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { - NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); - NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; - NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; - - [self _insertNodes:batchedNodes atIndexPaths:batchedIndexPaths withAnimationOption:animationOption]; - } -} - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption -{ - [self performDataFetchingWithBlock:^{ - // sort indexPath to avoid messing up the index when inserting in several batches + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + + // sort indexPath in order to avoid messing up the index when deleting NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - for (NSUInteger i = 0; i < sortedIndexPaths.count; i++) { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]]; - } - [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; + [_editingTransactionQueue addOperationWithBlock:^{ + [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; + }]; }]; } -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - // sort indexPath in order to avoid messing up the index when deleting - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - - dispatch_async([ASDataController sizingQueue], ^{ - [self asyncUpdateDataWithBlock:^{ - DELETE_NODES(_nodes, sortedIndexPaths, animationOption); - }]; - }); -} - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption -{ - [self performDataFetchingWithBlock:^{ - // The reloading operation required reloading the data - // Loading data in the calling thread - NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; - }]; - - dispatch_async([ASDataController sizingQueue], ^{ - [self syncUpdateDataWithBlock:^{ - DELETE_NODES(_nodes, indexPaths, animationOption); + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + + // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. + [self accessDataSourceWithBlock:^{ + NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + [indexPaths sortedArrayUsingSelector:@selector(compare:)]; + [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; }]; - - [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; - }); + + [_editingTransactionQueue addOperationWithBlock:^{ + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; }]; } -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOption:(ASDataControllerAnimationOptions)animationOption +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - dispatch_async([ASDataController sizingQueue], ^{ - [self asyncUpdateDataWithBlock:^{ - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_nodes, [NSArray arrayWithObject:indexPath]); + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + + [_editingTransactionQueue addOperationWithBlock:^{ + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, [NSArray arrayWithObject:indexPath]); NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; - DELETE_NODES(_nodes, indexPaths, animationOption); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; // Don't re-calculate size for moving NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath]; - INSERT_NODES(_nodes, newIndexPaths, nodes, animationOption); + [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; }]; - }); -} - -- (void)reloadDataWithAnimationOption:(ASDataControllerAnimationOptions)animationOption -{ - [self performDataFetchingWithBlock:^{ - // Fetching data in calling thread - NSMutableArray *updatedNodes = [[NSMutableArray alloc] init]; - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init]; - - NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; - for (NSUInteger i = 0; i < sectionNum; i++) { - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i]; - - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; - for (NSUInteger j = 0; j < rowNum; j++) { - NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; - [updatedIndexPaths addObject:indexPath]; - [updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; - } - } - - dispatch_async([ASDataController sizingQueue], ^{ - [self syncUpdateDataWithBlock:^{ - - NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_nodes); - DELETE_NODES(_nodes, indexPaths, animationOption); - - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _nodes.count)]; - DELETE_SECTIONS(_nodes, indexSet, animationOption); - - - // Insert section - - NSMutableArray *sections = [[NSMutableArray alloc] initWithCapacity:sectionNum]; - for (int i = 0; i < sectionNum; i++) { - [sections addObject:[[NSMutableArray alloc] init]]; - } - - INSERT_SECTIONS(_nodes, [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, sectionNum)], sections, animationOption); - - }]; - - [self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption]; - }); }]; } -#pragma mark - Data Querying +#pragma mark - Data Querying (External API) - (NSUInteger)numberOfSections { ASDisplayNodeAssertMainThread(); - return [_nodes count]; + return [_completedNodes count]; } - (NSUInteger)numberOfRowsInSection:(NSUInteger)section { ASDisplayNodeAssertMainThread(); - return [_nodes[section] count]; + return [_completedNodes[section] count]; } - (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssertMainThread(); - return _nodes[indexPath.section][indexPath.row]; + return _completedNodes[indexPath.section][indexPath.row]; } - (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); - return ASFindElementsInMultidimensionalArrayAtIndexPaths(_nodes, [indexPaths sortedArrayUsingSelector:@selector(compare:)]); + return ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, [indexPaths sortedArrayUsingSelector:@selector(compare:)]); +} + +- (NSArray *)completedNodes +{ + ASDisplayNodeAssertMainThread(); + return _completedNodes; +} + +#pragma mark - Dealloc + +- (void)dealloc +{ + ASDisplayNodeAssertMainThread(); + [_completedNodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) { + [section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) { + if (node.isNodeLoaded) { + if (node.layerBacked) { + [node.layer removeFromSuperlayer]; + } else { + [node.view removeFromSuperview]; + } + } + }]; + }]; } @end diff --git a/AsyncDisplayKit/Details/ASFlowLayoutController.h b/AsyncDisplayKit/Details/ASFlowLayoutController.h index 2524c30509..9b61a37b7d 100644 --- a/AsyncDisplayKit/Details/ASFlowLayoutController.h +++ b/AsyncDisplayKit/Details/ASFlowLayoutController.h @@ -6,7 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#import #import @@ -15,19 +15,21 @@ typedef NS_ENUM(NSUInteger, ASFlowLayoutDirection) { ASFlowLayoutDirectionHorizontal, }; +@protocol ASFlowLayoutControllerDataSource + +- (NSArray *)completedNodes; // This provides access to ASDataController's _completedNodes multidimensional array. + +@end + /** - * The controller for flow layout. + * An optimized flow layout controller that supports only vertical or horizontal scrolling, not simultaneously two-dimensional scrolling. + * It is used for all ASTableViews, and may be used with ASCollectionView. */ -@interface ASFlowLayoutController : NSObject - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; +@interface ASFlowLayoutController : ASAbstractLayoutController @property (nonatomic, readonly, assign) ASFlowLayoutDirection layoutDirection; +@property (nonatomic, readwrite, weak) id dataSource; - (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection; -@property (nonatomic, assign) ASRangeTuningParameters tuningParameters ASDISPLAYNODE_DEPRECATED; - @end diff --git a/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/AsyncDisplayKit/Details/ASFlowLayoutController.mm index eb07c1c002..52b9e6e880 100644 --- a/AsyncDisplayKit/Details/ASFlowLayoutController.mm +++ b/AsyncDisplayKit/Details/ASFlowLayoutController.mm @@ -7,162 +7,73 @@ */ #import "ASFlowLayoutController.h" +#import "ASAssert.h" +#import "ASDisplayNode.h" +#import "ASIndexPath.h" #include #include #include -#import "ASAssert.h" - static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3; -@interface ASFlowLayoutController() { - std::vector > _nodeSizes; - - std::pair _visibleRangeStartPos; - std::pair _visibleRangeEndPos; - - std::vector> _rangeStartPos; - std::vector> _rangeEndPos; - - std::vector _tuningParameters; +@interface ASFlowLayoutController() +{ + ASIndexPathRange _visibleRange; + std::vector _rangesByType; // All ASLayoutRangeTypes besides visible. } @end @implementation ASFlowLayoutController -- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection { +- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection +{ if (!(self = [super init])) { return nil; } - _layoutDirection = layoutDirection; - - _tuningParameters = std::vector(ASLayoutRangeTypeCount); - _tuningParameters[ASLayoutRangeTypePreload] = { - .leadingBufferScreenfuls = 2, - .trailingBufferScreenfuls = 1 - }; - _tuningParameters[ASLayoutRangeTypeRender] = { - .leadingBufferScreenfuls = 3, - .trailingBufferScreenfuls = 2 - }; - + _rangesByType = std::vector(ASLayoutRangeTypeCount); return self; } -#pragma mark - Tuning Parameters - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters"); - return _tuningParameters[rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters"); - _tuningParameters[rangeType] = tuningParameters; -} - -// Support for the deprecated tuningParameters property -- (ASRangeTuningParameters)tuningParameters -{ - return [self tuningParametersForRangeType:ASLayoutRangeTypeRender]; -} - -// Support for the deprecated tuningParameters property -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters -{ - [self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender]; -} - -#pragma mark - Editing - -- (void)insertNodesAtIndexPaths:(NSArray *)indexPaths withSizes:(NSArray *)nodeSizes -{ - ASDisplayNodeAssert(indexPaths.count == nodeSizes.count, @"Inconsistent index paths and node size"); - - [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - std::vector &v = _nodeSizes[indexPath.section]; - v.insert(v.begin() + indexPath.row, [(NSValue *)nodeSizes[idx] CGSizeValue]); - }]; -} - -- (void)deleteNodesAtIndexPaths:(NSArray *)indexPaths -{ - [indexPaths enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - std::vector &v = _nodeSizes[indexPath.section]; - v.erase(v.begin() + indexPath.row); - }]; -} - -- (void)insertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet -{ - __block int cnt = 0; - [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - NSArray *nodes = sections[cnt++]; - std::vector v; - v.reserve(nodes.count); - - for (int i = 0; i < nodes.count; i++) { - v.push_back([nodes[i] CGSizeValue]); - } - - _nodeSizes.insert(_nodeSizes.begin() + idx, v); - }]; -} - -- (void)deleteSectionsAtIndexSet:(NSIndexSet *)indexSet { - [indexSet enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *stop) - { - _nodeSizes.erase(_nodeSizes.begin() +idx); - }]; -} - #pragma mark - Visible Indices - (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType { - if (!indexPaths.count) { + if (!indexPaths.count || rangeType >= _rangesByType.size()) { return NO; } - std::pair rangeStartPos, rangeEndPos; - - if (rangeType < _rangeStartPos.size() && rangeType < _rangeEndPos.size()) { - rangeStartPos = _rangeStartPos[rangeType]; - rangeEndPos = _rangeEndPos[rangeType]; - } - - std::pair startPos, endPos; - ASFindIndexPathRange(indexPaths, startPos, endPos); - - if (rangeStartPos >= startPos || rangeEndPos <= endPos) { + ASIndexPathRange existingRange = _rangesByType[rangeType]; + ASIndexPathRange newRange = [self indexPathRangeForIndexPaths:indexPaths]; + + ASIndexPath maximumStart = ASIndexPathMaximum(existingRange.start, newRange.start); + ASIndexPath minimumEnd = ASIndexPathMinimum(existingRange.end, newRange.end); + + if (ASIndexPathEqualToIndexPath(maximumStart, existingRange.start) || ASIndexPathEqualToIndexPath(minimumEnd, existingRange.end)) { return YES; } - return ASFlowLayoutDistance(startPos, _visibleRangeStartPos, _nodeSizes) > ASFlowLayoutDistance(_visibleRangeStartPos, rangeStartPos, _nodeSizes) * kASFlowLayoutControllerRefreshingThreshold || - ASFlowLayoutDistance(endPos, _visibleRangeEndPos, _nodeSizes) > ASFlowLayoutDistance(_visibleRangeEndPos, rangeEndPos, _nodeSizes) * kASFlowLayoutControllerRefreshingThreshold; -} - -- (BOOL)shouldUpdateForVisibleIndexPath:(NSArray *)indexPaths - viewportSize:(CGSize)viewportSize -{ - return [self shouldUpdateForVisibleIndexPaths:indexPaths viewportSize:viewportSize rangeType:ASLayoutRangeTypeRender]; + NSInteger newStartDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.start, newRange.start)]; + NSInteger existingStartDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.start, existingRange.start)] * kASFlowLayoutControllerRefreshingThreshold; + + NSInteger newEndDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.end, newRange.end)]; + NSInteger existingEndDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.end, existingRange.end)] * kASFlowLayoutControllerRefreshingThreshold; + + return (newStartDelta > existingStartDelta) || (newEndDelta > existingEndDelta); } - (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths { - ASFindIndexPathRange(indexPaths, _visibleRangeStartPos, _visibleRangeEndPos); + _visibleRange = [self indexPathRangeForIndexPaths:indexPaths]; } /** * IndexPath array for the element in the working range. */ -- (NSSet *)indexPathsForScrolling:(enum ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType { CGFloat viewportScreenMetric; ASScrollDirection leadingDirection; @@ -183,105 +94,134 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3; CGFloat backScreens = scrollDirection == leadingDirection ? tuningParameters.leadingBufferScreenfuls : tuningParameters.trailingBufferScreenfuls; CGFloat frontScreens = scrollDirection == leadingDirection ? tuningParameters.trailingBufferScreenfuls : tuningParameters.leadingBufferScreenfuls; - std::pair startIter = ASFindIndexForRange(_nodeSizes, _visibleRangeStartPos, - backScreens * viewportScreenMetric, _layoutDirection); - std::pair endIter = ASFindIndexForRange(_nodeSizes, _visibleRangeEndPos, frontScreens * viewportScreenMetric, _layoutDirection); + + ASIndexPath startPath = [self findIndexPathAtDistance:(-backScreens * viewportScreenMetric) fromIndexPath:_visibleRange.start]; + ASIndexPath endPath = [self findIndexPathAtDistance:(frontScreens * viewportScreenMetric) fromIndexPath:_visibleRange.end]; + ASDisplayNodeAssert(startPath.section <= endPath.section, @"startPath should never begin at a further position than endPath"); + NSMutableSet *indexPathSet = [[NSMutableSet alloc] init]; - while (startIter != endIter) { - [indexPathSet addObject:[NSIndexPath indexPathForRow:startIter.second inSection:startIter.first]]; - startIter.second++; + NSArray *completedNodes = [_dataSource completedNodes]; + + while (!ASIndexPathEqualToIndexPath(startPath, endPath)) { + [indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:startPath]]; + startPath.row++; - while (startIter.second == _nodeSizes[startIter.first].size() && startIter.first < _nodeSizes.size()) { - startIter.second = 0; - startIter.first++; + // Once we reach the end of the section, advance to the next one. Keep advancing if the next section is zero-sized. + while (startPath.row >= [(NSArray *)completedNodes[startPath.section] count] && startPath.section < completedNodes.count - 1) { + startPath.row = 0; + startPath.section++; + ASDisplayNodeAssert(startPath.section <= endPath.section, @"startPath should never reach a further section than endPath"); } } - [indexPathSet addObject:[NSIndexPath indexPathForRow:endIter.second inSection:endIter.first]]; + [indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:endPath]]; return indexPathSet; } -- (NSSet *)indexPathsForScrolling:(enum ASScrollDirection)scrollDirection - viewportSize:(CGSize)viewportSize -{ - return [self indexPathsForScrolling:scrollDirection viewportSize:viewportSize rangeType:ASLayoutRangeTypeRender]; -} - #pragma mark - Utility -static void ASFindIndexPathRange(NSArray *indexPaths, std::pair &startPos, std::pair &endPos) - +- (ASIndexPathRange)indexPathRangeForIndexPaths:(NSArray *)indexPaths { - NSIndexPath *initialIndexPath = [indexPaths firstObject]; - startPos = endPos = {initialIndexPath.section, initialIndexPath.row}; + // Set up an initial value so the MIN and MAX can work in the enumeration. + __block ASIndexPath currentIndexPath = [[indexPaths firstObject] ASIndexPathValue]; + __block ASIndexPathRange range; + range.start = currentIndexPath; + range.end = currentIndexPath; + [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - std::pair p(indexPath.section, indexPath.row); - startPos = MIN(startPos, p); - endPos = MAX(endPos, p); + currentIndexPath = [indexPath ASIndexPathValue]; + range.start = ASIndexPathMinimum(range.start, currentIndexPath); + range.end = ASIndexPathMaximum(range.end, currentIndexPath); }]; + return range; } -static const std::pair ASFindIndexForRange(const std::vector> &nodes, - const std::pair &pos, - CGFloat range, - ASFlowLayoutDirection layoutDirection) +- (ASIndexPath)findIndexPathAtDistance:(CGFloat)distance fromIndexPath:(ASIndexPath)start { - std::pair cur = pos, pre = pos; + // "end" is the index path we'll advance until we have gone far enough from "start" to reach "distance" + ASIndexPath end = start; + // "previous" will store one iteration before "end", in case we go too far and need to reset "end" to be "previous" + ASIndexPath previous = start; - if (range < 0.0 && cur.first >= 0 && cur.first < nodes.size() && cur.second >= 0 && cur.second < nodes[cur.first].size()) { - // search backward - while (range < 0.0 && cur.first >= 0 && cur.second >= 0) { - pre = cur; - CGSize size = nodes[cur.first][cur.second]; - range += layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height; - cur.second--; - while (cur.second < 0 && cur.first > 0) { - cur.second = (int)nodes[--cur.first].size() - 1; + NSArray *completedNodes = [_dataSource completedNodes]; + NSUInteger numberOfSections = [completedNodes count]; + NSUInteger numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count]; + + // If "distance" is negative, advance "end" backwards across rows and sections. + // Otherwise, advance forward. In either case, bring "distance" closer to zero by the dimension of each row passed. + if (distance < 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) { + while (distance < 0.0 && end.section >= 0 && end.row >= 0) { + previous = end; + ASDisplayNode *node = completedNodes[end.section][end.row]; + CGSize size = node.calculatedSize; + distance += (_layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height); + end.row--; + // If we've gone to a negative row, set to the last row of the previous section. While loop is required to handle empty sections. + while (end.row < 0 && end.section > 0) { + end.section--; + numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count]; + end.row = numberOfRowsInSection - 1; } } - if (cur.second < 0) { - cur = pre; + if (end.row < 0) { + end = previous; } } else { - // search forward - while (range > 0.0 && cur.first >= 0 && cur.first < nodes.size() && cur.second >= 0 && cur.second < nodes[cur.first].size()) { - pre = cur; - CGSize size = nodes[cur.first][cur.second]; - range -= layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height; + while (distance > 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) { + previous = end; + ASDisplayNode *node = completedNodes[end.section][end.row]; + CGSize size = node.calculatedSize; + distance -= _layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height; - cur.second++; - while (cur.second == nodes[cur.first].size() && cur.first < (int)nodes.size() - 1) { - cur.second = 0; - cur.first++; + end.row++; + // If we've gone beyond the section, reset to the beginning of the next section. While loop is required to handle empty sections. + while (end.row >= numberOfRowsInSection && end.section < numberOfSections - 1) { + end.row = 0; + end.section++; + numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count]; } } - if (cur.second == nodes[cur.first].size()) { - cur = pre; + if (end.row >= numberOfRowsInSection) { + end = previous; } } - return cur; + return end; } -static int ASFlowLayoutDistance(const std::pair &start, const std::pair &end, const std::vector> &nodes) +- (NSInteger)flowLayoutDistanceForRange:(ASIndexPathRange)range { - if (start == end) { + // This method should only be called with the range in proper order (start comes before end). + ASDisplayNodeAssert(ASIndexPathEqualToIndexPath(ASIndexPathMinimum(range.start, range.end), range.start), @"flowLayoutDistanceForRange: called with invalid range"); + + if (ASIndexPathEqualToIndexPath(range.start, range.end)) { return 0; - } else if (start > end) { - return - ASFlowLayoutDistance(end, start, nodes); } + + NSInteger totalRowCount = 0; + NSUInteger numberOfRowsInSection = 0; + NSArray *completedNodes = [_dataSource completedNodes]; - int res = 0; - - for (int i = start.first; i <= end.first; i++) { - res += (i == end.first ? end.second + 1 : nodes[i].size()) - (i == start.first ? start.second : 0); + for (NSInteger section = range.start.section; section <= range.end.section; section++) { + numberOfRowsInSection = [(NSArray *)completedNodes[section] count]; + totalRowCount += numberOfRowsInSection; + + if (section == range.start.section) { + // For the start section, make sure we don't count the rows before the start row. + totalRowCount -= range.start.row; + } else if (section == range.end.section) { + // For the start section, make sure we don't count the rows after the end row. + totalRowCount -= (numberOfRowsInSection - (range.end.row + 1)); + } } - - return res; + + ASDisplayNodeAssert(totalRowCount >= 0, @"totalRowCount in flowLayoutDistanceForRange: should not be negative"); + return totalRowCount; } @end diff --git a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm index efdf7c6eb6..acaa97f00e 100644 --- a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm +++ b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm @@ -10,7 +10,7 @@ #import -#import +#import "ASInternalHelpers.h" static const CGFloat kCornerRadius = 2.5; static const UIEdgeInsets padding = {2, 4, 1.5, 4}; @@ -23,7 +23,7 @@ static const UIEdgeInsets padding = {2, 4, 1.5, 4}; + (id)defaultValueForKey:(NSString *)key { if ([key isEqualToString:@"contentsScale"]) { - return @(ASDisplayNodeScreenScale()); + return @(ASScreenScale()); } else if ([key isEqualToString:@"highlightColor"]) { CGFloat components[] = {0, 0, 0, 0.25}; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); diff --git a/AsyncDisplayKit/Details/ASIndexPath.h b/AsyncDisplayKit/Details/ASIndexPath.h new file mode 100644 index 0000000000..9a307817db --- /dev/null +++ b/AsyncDisplayKit/Details/ASIndexPath.h @@ -0,0 +1,42 @@ +/* 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 + +typedef struct { + NSInteger section; + NSInteger row; +} ASIndexPath; + +typedef struct { + ASIndexPath start; + ASIndexPath end; +} ASIndexPathRange; + +ASDISPLAYNODE_EXTERN_C_BEGIN + +extern ASIndexPath ASIndexPathMake(NSInteger section, NSInteger row); + +extern BOOL ASIndexPathEqualToIndexPath(ASIndexPath first, ASIndexPath second); + +extern ASIndexPath ASIndexPathMinimum(ASIndexPath first, ASIndexPath second); + +extern ASIndexPath ASIndexPathMaximum(ASIndexPath first, ASIndexPath second); + +extern ASIndexPathRange ASIndexPathRangeMake(ASIndexPath first, ASIndexPath second); + +extern BOOL ASIndexPathRangeEqualToIndexPathRange(ASIndexPathRange first, ASIndexPathRange second); + +ASDISPLAYNODE_EXTERN_C_END + +@interface NSIndexPath (ASIndexPathAdditions) + ++ (NSIndexPath *)indexPathWithASIndexPath:(ASIndexPath)indexPath; +- (ASIndexPath)ASIndexPathValue; + +@end diff --git a/AsyncDisplayKit/Details/ASIndexPath.m b/AsyncDisplayKit/Details/ASIndexPath.m new file mode 100644 index 0000000000..d59a8bb8e1 --- /dev/null +++ b/AsyncDisplayKit/Details/ASIndexPath.m @@ -0,0 +1,73 @@ +/* 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 "ASIndexPath.h" + +#import + +ASIndexPath ASIndexPathMake(NSInteger section, NSInteger row) +{ + ASIndexPath indexPath; + indexPath.section = section; + indexPath.row = row; + return indexPath; +} + +BOOL ASIndexPathEqualToIndexPath(ASIndexPath first, ASIndexPath second) +{ + return (first.section == second.section && first.row == second.row); +} + +ASIndexPath ASIndexPathMinimum(ASIndexPath first, ASIndexPath second) +{ + if (first.section < second.section) { + return first; + } else if (first.section > second.section) { + return second; + } else { + return (first.row < second.row ? first : second); + } +} + +ASIndexPath ASIndexPathMaximum(ASIndexPath first, ASIndexPath second) +{ + if (first.section > second.section) { + return first; + } else if (first.section < second.section) { + return second; + } else { + return (first.row > second.row ? first : second); + } +} + +ASIndexPathRange ASIndexPathRangeMake(ASIndexPath first, ASIndexPath second) +{ + ASIndexPathRange range; + range.start = ASIndexPathMinimum(first, second); + range.end = ASIndexPathMaximum(first, second); + return range; +} + +BOOL ASIndexPathRangeEqualToIndexPathRange(ASIndexPathRange first, ASIndexPathRange second) +{ + return ASIndexPathEqualToIndexPath(first.start, second.start) && ASIndexPathEqualToIndexPath(first.end, second.end); +} + +@implementation NSIndexPath (ASIndexPathAdditions) + ++ (NSIndexPath *)indexPathWithASIndexPath:(ASIndexPath)indexPath +{ + return [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];; +} + +- (ASIndexPath)ASIndexPathValue +{ + return ASIndexPathMake(self.section, self.row); +} + +@end diff --git a/AsyncDisplayKit/Details/ASLayoutController.h b/AsyncDisplayKit/Details/ASLayoutController.h index 8a53f0ead4..b45efd8793 100644 --- a/AsyncDisplayKit/Details/ASLayoutController.h +++ b/AsyncDisplayKit/Details/ASLayoutController.h @@ -27,6 +27,18 @@ typedef struct { */ - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; +- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType; + +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType; + +@property (nonatomic, assign) ASRangeTuningParameters tuningParameters ASDISPLAYNODE_DEPRECATED; + +- (BOOL)shouldUpdateForVisibleIndexPath:(NSArray *)indexPath viewportSize:(CGSize)viewportSize ASDISPLAYNODE_DEPRECATED; + +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize ASDISPLAYNODE_DEPRECATED; + +@optional + - (void)insertNodesAtIndexPaths:(NSArray *)indexPaths withSizes:(NSArray *)nodeSizes; - (void)deleteNodesAtIndexPaths:(NSArray *)indexPaths; @@ -37,14 +49,4 @@ typedef struct { - (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths; -- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType; - -- (NSSet *)indexPathsForScrolling:(enum ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType; - -@property (nonatomic, assign) ASRangeTuningParameters tuningParameters ASDISPLAYNODE_DEPRECATED; - -- (BOOL)shouldUpdateForVisibleIndexPath:(NSArray *)indexPath viewportSize:(CGSize)viewportSize ASDISPLAYNODE_DEPRECATED; - -- (NSSet *)indexPathsForScrolling:(enum ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize ASDISPLAYNODE_DEPRECATED; - @end diff --git a/AsyncDisplayKit/Details/ASMultidimensionalArrayUtils.h b/AsyncDisplayKit/Details/ASMultidimensionalArrayUtils.h index b7fbbd8c3f..d5058a8622 100644 --- a/AsyncDisplayKit/Details/ASMultidimensionalArrayUtils.h +++ b/AsyncDisplayKit/Details/ASMultidimensionalArrayUtils.h @@ -44,7 +44,7 @@ extern NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray extern NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *MultidimensionalArray, NSIndexSet *indexSet); /** - * Reteurn all the index paths of mutable multidimensional array, in ascending order. + * Return all the index paths of mutable multidimensional array, in ascending order. */ extern NSArray *ASIndexPathsForMultidimensionalArray(NSArray *MultidimensionalArray); diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index e50be5706d..cdfb0568b4 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -19,18 +19,20 @@ /** * Working range controller. * - * Used internally by ASTableView and potentially by a future ASCollectionView. Observes the visible range, maintains - * a working range, and is responsible for handling AsyncDisplayKit machinery (sizing cell nodes, enqueueing and - * cancelling their asynchronous layout and display, and so on). + * Used internally by ASTableView and ASCollectionView. It is paired with ASDataController. + * It is designed to support custom scrolling containers as well. Observes the visible range, maintains + * "working ranges" to trigger network calls and rendering, and is responsible for driving asynchronous layout of cells. + * This includes cancelling those asynchronous operations as cells fall outside of the working ranges. */ @interface ASRangeController : ASDealloc2MainObject /** - * Notify the receiver that the visible range has been updated. + * Notify the range controller that the visible range has been updated. + * This is the primary input call that drives updating the working ranges, and triggering their actions. * * @see [ASRangeControllerDelegate rangeControllerVisibleNodeIndexPaths:] */ -- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(enum ASScrollDirection)scrollDirection; +- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection; /** * Add the sized node for `indexPath` as a subview of `contentView`. @@ -88,43 +90,21 @@ /** * Called for nodes insertion. */ -- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** * Called for nodes deletion. */ -- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** * Called for section insertion. */ -- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** * Called for section deletion. */ -- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption; - -@optional - -/** - * Called before nodes insertion. - */ -- (void)rangeController:(ASRangeController *)rangeController willInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption; - -/** - * Called before nodes deletion. - */ -- (void)rangeController:(ASRangeController *)rangeController willDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption; - -/** - * Called before section insertion. - */ -- (void)rangeController:(ASRangeController *)rangeController willInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption; - -/** - * Called before section deletion. - */ -- (void)rangeController:(ASRangeController *)rangeController willDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption; +- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; @end diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 781b5974a5..f41d9d291c 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -75,6 +75,7 @@ // coalesce these events -- handling them multiple times per runloop is noisy and expensive _queuedRangeUpdate = YES; + [self performSelector:@selector(updateVisibleNodeIndexPaths) withObject:nil afterDelay:0 @@ -92,7 +93,9 @@ CGSize viewportSize = [_delegate rangeControllerViewportSize:self]; // the layout controller needs to know what the current visible indices are to calculate range offsets - [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; + if ([_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]) { + [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; + } for (NSInteger i = 0; i < ASLayoutRangeTypeCount; i++) { ASLayoutRangeType rangeType = (ASLayoutRangeType)i; @@ -108,6 +111,7 @@ NSMutableSet *removedIndexPaths = _rangeIsValid ? [[_rangeTypeIndexPaths objectForKey:rangeKey] mutableCopy] : [NSMutableSet set]; [removedIndexPaths minusSet:indexPaths]; [removedIndexPaths minusSet:visibleNodePathsSet]; + if (removedIndexPaths.count) { NSArray *removedNodes = [_delegate rangeController:self nodesAtIndexPaths:[removedIndexPaths allObjects]]; [removedNodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx, BOOL *stop) { @@ -126,7 +130,7 @@ if ([self shouldSkipVisibleNodesForRangeType:rangeType]) { [addedIndexPaths minusSet:visibleNodePathsSet]; } - + if (addedIndexPaths.count) { NSArray *addedNodes = [_delegate rangeController:self nodesAtIndexPaths:[addedIndexPaths allObjects]]; [addedNodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx, BOOL *stop) { @@ -178,15 +182,7 @@ }); } -- (void)dataController:(ASDataController *)dataController willInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - ASDisplayNodePerformBlockOnMainThread(^{ - if ([_delegate respondsToSelector:@selector(rangeController:willInsertNodesAtIndexPaths:withAnimationOption:)]) { - [_delegate rangeController:self willInsertNodesAtIndexPaths:indexPaths withAnimationOption:animationOption]; - } - }); -} - -- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption { +- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); NSMutableArray *nodeSizes = [NSMutableArray arrayWithCapacity:nodes.count]; @@ -195,37 +191,19 @@ }]; ASDisplayNodePerformBlockOnMainThread(^{ - [_layoutController insertNodesAtIndexPaths:indexPaths withSizes:nodeSizes]; - [_delegate rangeController:self didInsertNodesAtIndexPaths:indexPaths withAnimationOption:animationOption]; _rangeIsValid = NO; + [_delegate rangeController:self didInsertNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; }); } -- (void)dataController:(ASDataController *)dataController willDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption { +- (void)dataController:(ASDataController *)dataController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodePerformBlockOnMainThread(^{ - if ([_delegate respondsToSelector:@selector(rangeController:willDeleteNodesAtIndexPaths:withAnimationOption:)]) { - [_delegate rangeController:self willDeleteNodesAtIndexPaths:indexPaths withAnimationOption:animationOption]; - } - }); -} - -- (void)dataController:(ASDataController *)dataController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - ASDisplayNodePerformBlockOnMainThread(^{ - [_layoutController deleteNodesAtIndexPaths:indexPaths]; - [_delegate rangeController:self didDeleteNodesAtIndexPaths:indexPaths withAnimationOption:animationOption]; _rangeIsValid = NO; + [_delegate rangeController:self didDeleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; }); } -- (void)dataController:(ASDataController *)dataController willInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - ASDisplayNodePerformBlockOnMainThread(^{ - if ([_delegate respondsToSelector:@selector(rangeController:willInsertSectionsAtIndexSet:withAnimationOption:)]) { - [_delegate rangeController:self willInsertSectionsAtIndexSet:indexSet withAnimationOption:animationOption]; - } - }); -} - -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption { +- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); NSMutableArray *sectionNodeSizes = [NSMutableArray arrayWithCapacity:sections.count]; @@ -239,25 +217,15 @@ }]; ASDisplayNodePerformBlockOnMainThread(^{ - [_layoutController insertSections:sectionNodeSizes atIndexSet:indexSet]; - [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOption:animationOption]; _rangeIsValid = NO; + [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } -- (void)dataController:(ASDataController *)dataController willDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption { +- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodePerformBlockOnMainThread(^{ - if ([_delegate respondsToSelector:@selector(rangeController:willDeleteSectionsAtIndexSet:withAnimationOption:)]) { - [_delegate rangeController:self willDeleteSectionsAtIndexSet:indexSet withAnimationOption:animationOption]; - } - }); -} - -- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - ASDisplayNodePerformBlockOnMainThread(^{ - [_layoutController deleteSectionsAtIndexSet:indexSet]; - [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOption:animationOption]; _rangeIsValid = NO; + [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } diff --git a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm index d18fef070c..d30f6c1a40 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm @@ -16,13 +16,13 @@ - (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType { ASDisplayNodeAssert(rangeType == ASLayoutRangeTypePreload, @"Preload delegate should not handle other ranges"); - [node recursivelyFetchRemoteData]; + [node recursivelyFetchData]; } - (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType { ASDisplayNodeAssert(rangeType == ASLayoutRangeTypePreload, @"Preload delegate should not handle other ranges"); - [node recursivelyClearRemoteData]; + [node recursivelyClearFetchedData]; } @end diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index ac83bcda3c..b9cf41c87a 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -19,14 +19,15 @@ ASDisplayNodeAssertMainThread(); // we add nodes' views to this invisible window to start async rendering + // TODO: Replace this with directly triggering display https://github.com/facebook/AsyncDisplayKit/issues/315 static UIWindow *workingWindow = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ workingWindow = [[UIWindow alloc] initWithFrame:CGRectZero]; workingWindow.windowLevel = UIWindowLevelNormal - 1000; workingWindow.userInteractionEnabled = NO; - workingWindow.clipsToBounds = YES; workingWindow.hidden = YES; + workingWindow.alpha = 0.0; }); return workingWindow; } @@ -50,7 +51,7 @@ [node recursivelySetDisplaySuspended:YES]; [node.view removeFromSuperview]; - [node recursivelyClearRendering]; + [node recursivelyClearContents]; } @end diff --git a/AsyncDisplayKit/Details/ASScrollDirection.h b/AsyncDisplayKit/Details/ASScrollDirection.h index 5459feffe8..a46295cf9e 100644 --- a/AsyncDisplayKit/Details/ASScrollDirection.h +++ b/AsyncDisplayKit/Details/ASScrollDirection.h @@ -8,10 +8,27 @@ #import -typedef NS_ENUM(NSInteger, ASScrollDirection) { - ASScrollDirectionNone, - ASScrollDirectionRight, - ASScrollDirectionLeft, - ASScrollDirectionUp, - ASScrollDirectionDown, +#import "ASBaseDefines.h" + +typedef NS_OPTIONS(NSInteger, ASScrollDirection) { + ASScrollDirectionNone = 0, + ASScrollDirectionRight = 1 << 0, + ASScrollDirectionLeft = 1 << 1, + ASScrollDirectionUp = 1 << 2, + ASScrollDirectionDown = 1 << 3 }; + +extern const ASScrollDirection ASScrollDirectionHorizontalDirections; +extern const ASScrollDirection ASScrollDirectionVerticalDirections; + +ASDISPLAYNODE_EXTERN_C_BEGIN + +BOOL ASScrollDirectionContainsVerticalDirection(ASScrollDirection scrollDirection); +BOOL ASScrollDirectionContainsHorizontalDirection(ASScrollDirection scrollDirection); + +BOOL ASScrollDirectionContainsRight(ASScrollDirection scrollDirection); +BOOL ASScrollDirectionContainsLeft(ASScrollDirection scrollDirection); +BOOL ASScrollDirectionContainsUp(ASScrollDirection scrollDirection); +BOOL ASScrollDirectionContainsDown(ASScrollDirection scrollDirection); + +ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Details/ASScrollDirection.m b/AsyncDisplayKit/Details/ASScrollDirection.m new file mode 100644 index 0000000000..40698d4279 --- /dev/null +++ b/AsyncDisplayKit/Details/ASScrollDirection.m @@ -0,0 +1,36 @@ +/* Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "ASScrollDirection.h" + +const ASScrollDirection ASScrollDirectionHorizontalDirections = ASScrollDirectionLeft | ASScrollDirectionRight; +const ASScrollDirection ASScrollDirectionVerticalDirections = ASScrollDirectionUp | ASScrollDirectionDown; + +BOOL ASScrollDirectionContainsVerticalDirection(ASScrollDirection scrollDirection) { + return (scrollDirection & ASScrollDirectionVerticalDirections) != 0; +} + +BOOL ASScrollDirectionContainsHorizontalDirection(ASScrollDirection scrollDirection) { + return (scrollDirection & ASScrollDirectionHorizontalDirections) != 0; +} + +BOOL ASScrollDirectionContainsRight(ASScrollDirection scrollDirection) { + return (scrollDirection & ASScrollDirectionRight) != 0; +} + +BOOL ASScrollDirectionContainsLeft(ASScrollDirection scrollDirection) { + return (scrollDirection & ASScrollDirectionLeft) != 0; +} + +BOOL ASScrollDirectionContainsUp(ASScrollDirection scrollDirection) { + return (scrollDirection & ASScrollDirectionUp) != 0; +} + +BOOL ASScrollDirectionContainsDown(ASScrollDirection scrollDirection) { + return (scrollDirection & ASScrollDirectionDown) != 0; +} diff --git a/AsyncDisplayKit/Details/ASTextNodeRenderer.h b/AsyncDisplayKit/Details/ASTextNodeRenderer.h index dd57a99421..3b80bb1677 100644 --- a/AsyncDisplayKit/Details/ASTextNodeRenderer.h +++ b/AsyncDisplayKit/Details/ASTextNodeRenderer.h @@ -45,6 +45,12 @@ typedef NS_ENUM(NSUInteger, ASTextNodeRendererMeasureOption) { */ @interface ASTextNodeRenderer : NSObject +- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString + truncationString:(NSAttributedString *)truncationString + truncationMode:(NSLineBreakMode)truncationMode + maximumLineCount:(NSUInteger)maximumLineCount + exclusionPaths:(NSArray *)exclusionPaths + constrainedSize:(CGSize)constrainedSize; /* * Designated Initializer * diff --git a/AsyncDisplayKit/Details/ASTextNodeRenderer.mm b/AsyncDisplayKit/Details/ASTextNodeRenderer.mm index e76c8bdbd9..b17ae2833f 100644 --- a/AsyncDisplayKit/Details/ASTextNodeRenderer.mm +++ b/AsyncDisplayKit/Details/ASTextNodeRenderer.mm @@ -39,6 +39,8 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; NSLayoutManager *_layoutManager; NSTextStorage *_textStorage; NSTextContainer *_textContainer; + + NSArray *_exclusionPaths; } #pragma mark - Initialization @@ -47,6 +49,7 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; truncationString:(NSAttributedString *)truncationString truncationMode:(NSLineBreakMode)truncationMode maximumLineCount:(NSUInteger)maximumLineCount + exclusionPaths:(NSArray *)exclusionPaths constrainedSize:(CGSize)constrainedSize { if (self = [super init]) { @@ -57,11 +60,22 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; _maximumLineCount = maximumLineCount; + _exclusionPaths = exclusionPaths; + _constrainedSize = constrainedSize; } return self; } +- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString + truncationString:(NSAttributedString *)truncationString + truncationMode:(NSLineBreakMode)truncationMode + maximumLineCount:(NSUInteger)maximumLineCount + constrainedSize:(CGSize)constrainedSize +{ + return [self initWithAttributedString:attributedString truncationString:truncationString truncationMode:truncationMode maximumLineCount:maximumLineCount exclusionPaths:nil constrainedSize:constrainedSize]; +} + /* * Use this method to lazily construct the TextKit components. */ @@ -97,6 +111,8 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; // Set maximum number of lines _textContainer.maximumNumberOfLines = _maximumLineCount; + _textContainer.exclusionPaths = _exclusionPaths; + [_layoutManager addTextContainer:_textContainer]; ASDN::StaticMutexUnlocker gu(mutex); diff --git a/AsyncDisplayKit/Details/ASTextNodeWordKerner.m b/AsyncDisplayKit/Details/ASTextNodeWordKerner.m index 0fd7d25004..ffd9655a91 100644 --- a/AsyncDisplayKit/Details/ASTextNodeWordKerner.m +++ b/AsyncDisplayKit/Details/ASTextNodeWordKerner.m @@ -90,7 +90,7 @@ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ spaceSizes = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:1]; - mapQueue = dispatch_queue_create("com.facebook.AsyncDisplayKit.wordKerningQueue", DISPATCH_QUEUE_SERIAL); + mapQueue = dispatch_queue_create("org.AsyncDisplayKit.wordKerningQueue", DISPATCH_QUEUE_SERIAL); }); CGFloat ordinarySpaceWidth; UIFont *font = [layoutManager.textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL]; diff --git a/AsyncDisplayKit/Details/CGRect+ASConvenience.h b/AsyncDisplayKit/Details/CGRect+ASConvenience.h new file mode 100644 index 0000000000..a9cc714a31 --- /dev/null +++ b/AsyncDisplayKit/Details/CGRect+ASConvenience.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "ASBaseDefines.h" + +ASDISPLAYNODE_EXTERN_C_BEGIN + +CGRect asdk_CGRectExpandHorizontally(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier); +CGRect asdk_CGRectExpandVertically(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier); + +ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Details/CGRect+ASConvenience.m b/AsyncDisplayKit/Details/CGRect+ASConvenience.m new file mode 100644 index 0000000000..171f3d3986 --- /dev/null +++ b/AsyncDisplayKit/Details/CGRect+ASConvenience.m @@ -0,0 +1,31 @@ +/* Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "CGRect+ASConvenience.h" + +CGRect asdk_CGRectExpandHorizontally(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier) { + CGFloat negativeDirectionWidth = negativeMultiplier * rect.size.width; + CGFloat positiveDirectionWidth = positiveMultiplier * rect.size.width; + CGFloat width = negativeDirectionWidth + rect.size.width + positiveDirectionWidth; + CGFloat originX = rect.origin.x - negativeDirectionWidth; + return CGRectMake(originX, + rect.origin.y, + width, + rect.size.height); +} + +CGRect asdk_CGRectExpandVertically(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier) { + CGFloat negativeDirectionHeight = negativeMultiplier * rect.size.height; + CGFloat positiveDirectionHeight = positiveMultiplier * rect.size.height; + CGFloat height = negativeDirectionHeight + rect.size.height + positiveDirectionHeight; + CGFloat originY = rect.origin.y - negativeDirectionHeight; + return CGRectMake(rect.origin.x, + originY, + rect.size.width, + height); +} diff --git a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h new file mode 100644 index 0000000000..1853be6fb7 --- /dev/null +++ b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h @@ -0,0 +1,15 @@ +/* Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface UICollectionViewLayout (ASConvenience) + +- (BOOL)asdk_isFlowLayout; + +@end diff --git a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m new file mode 100644 index 0000000000..a401ec549e --- /dev/null +++ b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m @@ -0,0 +1,17 @@ +/* Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "UICollectionViewLayout+ASConvenience.h" + +@implementation UICollectionViewLayout (ASConvenience) + +- (BOOL)asdk_isFlowLayout { + return [self isKindOfClass:UICollectionViewFlowLayout.class]; +} + +@end diff --git a/AsyncDisplayKit/Details/UIView+ASConvenience.h b/AsyncDisplayKit/Details/UIView+ASConvenience.h index 2b7df0c476..d1f0032469 100644 --- a/AsyncDisplayKit/Details/UIView+ASConvenience.h +++ b/AsyncDisplayKit/Details/UIView+ASConvenience.h @@ -75,6 +75,9 @@ @property (atomic, assign) BOOL shouldGroupAccessibilityChildren; */ +// Accessibility identification support +@property (nonatomic, copy) NSString *accessibilityIdentifier; + @end @interface CALayer (ASDisplayNodeLayer) diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index 9240c0a42a..1d9b023983 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -93,10 +93,13 @@ [super layoutSublayers]; ASDisplayNode *node = self.asyncdisplaykit_node; - // If our associated node is layer-backed, we cannot rely on the view's -layoutSubviews calling the node's -layout implementation, so do it ourselves. - if (node.isLayerBacked) { - ASDisplayNodeAssertMainThread(); + if (ASDisplayNodeThreadIsMain()) { [node __layout]; + } else { + ASDisplayNodeFailAssert(@"not reached assertion"); + dispatch_async(dispatch_get_main_queue(), ^ { + [node __layout]; + }); } } @@ -126,7 +129,7 @@ static dispatch_queue_t displayQueue = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - displayQueue = dispatch_queue_create("com.facebook.AsyncDisplayKit.ASDisplayLayer.displayQueue", DISPATCH_QUEUE_CONCURRENT); + displayQueue = dispatch_queue_create("org.AsyncDisplayKit.ASDisplayLayer.displayQueue", DISPATCH_QUEUE_CONCURRENT); // we use the highpri queue to prioritize UI rendering over other async operations dispatch_set_target_queue(displayQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); }); diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 8a71013fbe..8b5300269d 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -122,19 +122,6 @@ } } -- (void)layoutSubviews -{ - if (ASDisplayNodeThreadIsMain()) { - [_node __layout]; - } else { - // FIXME: CRASH This should not be happening because of the way we gate -setNeedsLayout, but it has been seen. - ASDisplayNodeFailAssert(@"not reached assertion"); - dispatch_async(dispatch_get_main_queue(), ^ { - [_node __layout]; - }); - } -} - - (UIViewContentMode)contentMode { return ASDisplayNodeUIContentModeFromCAContentsGravity(self.layer.contentsGravity); @@ -256,6 +243,14 @@ [_node tintColorDidChange]; } +- (BOOL)canBecomeFirstResponder { + return [_node canBecomeFirstResponder]; +} + +- (BOOL)canResignFirstResponder { + return [_node canResignFirstResponder]; +} + - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { // We forward responder-chain actions to our node if we can't handle them ourselves. See -targetForAction:withSender:. diff --git a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h new file mode 100644 index 0000000000..508f7c8b28 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h @@ -0,0 +1,25 @@ +/* + * 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 + +/** + Lays out a single layoutable child, then lays out a background layoutable instance behind it stretched to its size. + */ +@interface ASBackgroundLayoutSpec : ASLayoutSpec + +/** + @param child A child that is laid out to determine the size of this spec. If this is nil, then this method + returns nil. + @param background A layoutable object that is laid out behind the child. May be nil, in which case the background is omitted. + */ ++ (instancetype)newWithChild:(id)child background:(id)background; + +@end diff --git a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm new file mode 100644 index 0000000000..135fc372f4 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm @@ -0,0 +1,61 @@ +/* + * 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 "ASBackgroundLayoutSpec.h" + +#import "ASAssert.h" +#import "ASBaseDefines.h" + +@interface ASBackgroundLayoutSpec () +{ + id _child; + id _background; +} +@end + +@implementation ASBackgroundLayoutSpec + ++ (instancetype)newWithChild:(id)child background:(id)background +{ + if (child == nil) { + return nil; + } + ASBackgroundLayoutSpec *spec = [super new]; + spec->_child = child; + spec->_background = background; + return spec; +} + ++ (instancetype)new +{ + ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); +} + +/** + First layout the contents, then fit the background image. + */ +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + ASLayout *contentsLayout = [_child measureWithSizeRange:constrainedSize]; + + NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:2]; + if (_background) { + // Size background to exactly the same size. + ASLayout *backgroundLayout = [_background measureWithSizeRange:{contentsLayout.size, contentsLayout.size}]; + backgroundLayout.position = CGPointZero; + [sublayouts addObject:backgroundLayout]; + } + contentsLayout.position = CGPointZero; + [sublayouts addObject:contentsLayout]; + + return [ASLayout newWithLayoutableObject:self size:contentsLayout.size sublayouts:sublayouts]; +} + +@end diff --git a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h new file mode 100644 index 0000000000..5afc73197a --- /dev/null +++ b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h @@ -0,0 +1,46 @@ +/* + * 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 + +typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecCenteringOptions) { + /** The child is positioned in {0,0} relatively to the layout bounds */ + ASCenterLayoutSpecCenteringNone = 0, + /** The child is centered along the X axis */ + ASCenterLayoutSpecCenteringX = 1 << 0, + /** The child is centered along the Y axis */ + ASCenterLayoutSpecCenteringY = 1 << 1, + /** Convenience option to center both along the X and Y axis */ + ASCenterLayoutSpecCenteringXY = ASCenterLayoutSpecCenteringX | ASCenterLayoutSpecCenteringY +}; + +typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecSizingOptions) { + /** The spec will take up the maximum size possible */ + ASCenterLayoutSpecSizingOptionDefault, + /** The spec will take up the minimum size possible along the X axis */ + ASCenterLayoutSpecSizingOptionMinimumX = 1 << 0, + /** The spec will take up the minimum size possible along the Y axis */ + ASCenterLayoutSpecSizingOptionMinimumY = 1 << 1, + /** Convenience option to take up the minimum size along both the X and Y axis */ + ASCenterLayoutSpecSizingOptionMinimumXY = ASCenterLayoutSpecSizingOptionMinimumX | ASCenterLayoutSpecSizingOptionMinimumY, +}; + +/** Lays out a single layoutable child and position it so that it is centered into the layout bounds. */ +@interface ASCenterLayoutSpec : ASLayoutSpec + +/** + @param centeringOptions, see ASCenterLayoutSpecCenteringOptions. + @param child The child to center. + */ ++ (instancetype)newWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions + sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions + child:(id)child; + +@end diff --git a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm new file mode 100644 index 0000000000..0ef0f4848d --- /dev/null +++ b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm @@ -0,0 +1,78 @@ +/* + * 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 "ASCenterLayoutSpec.h" + +#import "ASInternalHelpers.h" + +@implementation ASCenterLayoutSpec +{ + ASCenterLayoutSpecCenteringOptions _centeringOptions; + ASCenterLayoutSpecSizingOptions _sizingOptions; + id _child; +} + ++ (instancetype)newWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions + sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions + child:(id)child +{ + ASCenterLayoutSpec *spec = [super new]; + if (spec) { + spec->_centeringOptions = centeringOptions; + spec->_sizingOptions = sizingOptions; + spec->_child = child; + } + return spec; +} + ++ (instancetype)new +{ + ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); +} + +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + CGSize size = { + constrainedSize.max.width, + constrainedSize.max.height + }; + + // Layout the child + const CGSize minChildSize = { + (_centeringOptions & ASCenterLayoutSpecCenteringX) != 0 ? 0 : constrainedSize.min.width, + (_centeringOptions & ASCenterLayoutSpecCenteringY) != 0 ? 0 : constrainedSize.min.height, + }; + ASLayout *sublayout = [_child measureWithSizeRange:ASSizeRangeMake(minChildSize, constrainedSize.max)]; + + // If we have an undetermined height or width, use the child size to define the layout + // size + size = ASSizeRangeClamp(constrainedSize, { + isnan(size.width) ? sublayout.size.width : size.width, + isnan(size.height) ? sublayout.size.height : size.height + }); + + // If minimum size options are set, attempt to shrink the size to the size of the child + size = ASSizeRangeClamp(constrainedSize, { + MIN(size.width, (_sizingOptions & ASCenterLayoutSpecSizingOptionMinimumX) != 0 ? sublayout.size.width : size.width), + MIN(size.height, (_sizingOptions & ASCenterLayoutSpecSizingOptionMinimumY) != 0 ? sublayout.size.height : size.height) + }); + + // Compute the centered postion for the child + BOOL shouldCenterAlongX = (_centeringOptions & ASCenterLayoutSpecCenteringX); + BOOL shouldCenterAlongY = (_centeringOptions & ASCenterLayoutSpecCenteringY); + sublayout.position = { + ASRoundPixelValue(shouldCenterAlongX ? (size.width - sublayout.size.width) * 0.5f : 0), + ASRoundPixelValue(shouldCenterAlongY ? (size.height - sublayout.size.height) * 0.5f : 0) + }; + + return [ASLayout newWithLayoutableObject:self size:size sublayouts:@[sublayout]]; +} + +@end diff --git a/AsyncDisplayKit/Layout/ASDimension.h b/AsyncDisplayKit/Layout/ASDimension.h new file mode 100644 index 0000000000..6c11aa7701 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASDimension.h @@ -0,0 +1,76 @@ +/* + * 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 +#import + +/** + A dimension relative to constraints to be provided in the future. + A RelativeDimension can be one of two types: + + "Points" - Just a number. It will always resolve to exactly this amount. This is the default type. + + "Percent" - Multiplied to a provided parent amount to resolve a final amount. + */ +typedef NS_ENUM(NSInteger, ASRelativeDimensionType) { + ASRelativeDimensionTypePoints, + ASRelativeDimensionTypePercent, +}; + +typedef struct { + ASRelativeDimensionType type; + CGFloat value; +} ASRelativeDimension; + +/** Expresses an inclusive range of sizes. Used to provide a simple constraint to layout. */ +typedef struct { + CGSize min; + CGSize max; +} ASSizeRange; + +extern ASRelativeDimension const ASRelativeDimensionUnconstrained; + +ASDISPLAYNODE_EXTERN_C_BEGIN + +#pragma mark ASRelativeDimension + +extern ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value); + +extern ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat points); + +extern ASRelativeDimension ASRelativeDimensionMakeWithPercent(CGFloat percent); + +extern ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension); + +extern BOOL ASRelativeDimensionEqualToRelativeDimension(ASRelativeDimension lhs, ASRelativeDimension rhs); + +extern NSString *NSStringFromASRelativeDimension(ASRelativeDimension dimension); + +extern CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat parent); + +#pragma mark - +#pragma mark ASSizeRange + +extern ASSizeRange ASSizeRangeMake(CGSize min, CGSize max); + +/** Clamps the provided CGSize between the [min, max] bounds of this ASSizeRange. */ +extern CGSize ASSizeRangeClamp(ASSizeRange sizeRange, CGSize size); + +/** + Intersects another size range. If the other size range does not overlap in either dimension, this size range + "wins" by returning a single point within its own range that is closest to the non-overlapping range. + */ +extern ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRange); + +extern BOOL ASSizeRangeEqualToSizeRange(ASSizeRange lhs, ASSizeRange rhs); + +extern NSString *NSStringFromASSizeRange(ASSizeRange sizeRange); + +ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Layout/ASDimension.mm b/AsyncDisplayKit/Layout/ASDimension.mm new file mode 100644 index 0000000000..a0737f6682 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASDimension.mm @@ -0,0 +1,128 @@ +/* + * 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 "ASDimension.h" +#import "ASAssert.h" + +ASRelativeDimension const ASRelativeDimensionUnconstrained = {}; + +#pragma mark ASRelativeDimension + +ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) +{ + if (type == ASRelativeDimensionTypePoints) { ASDisplayNodeCAssertPositiveReal(@"Points", value); } + ASRelativeDimension dimension; dimension.type = type; dimension.value = value; return dimension; +} + +ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat points) +{ + return ASRelativeDimensionMake(ASRelativeDimensionTypePoints, points); +} + +ASRelativeDimension ASRelativeDimensionMakeWithPercent(CGFloat percent) +{ + return ASRelativeDimensionMake(ASRelativeDimensionTypePercent, percent); +} + +ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension) +{ + return ASRelativeDimensionMake(aDimension.type, aDimension.value); +} + +BOOL ASRelativeDimensionEqualToRelativeDimension(ASRelativeDimension lhs, ASRelativeDimension rhs) +{ + return lhs.type == rhs.type && lhs.value == rhs.value; +} + +NSString *NSStringFromASRelativeDimension(ASRelativeDimension dimension) +{ + switch (dimension.type) { + case ASRelativeDimensionTypePoints: + return [NSString stringWithFormat:@"%.0fpt", dimension.value]; + case ASRelativeDimensionTypePercent: + return [NSString stringWithFormat:@"%.0f%%", dimension.value * 100.0]; + } +} + +CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat parent) +{ + switch (dimension.type) { + case ASRelativeDimensionTypePoints: + return dimension.value; + case ASRelativeDimensionTypePercent: + return round(dimension.value * parent); + } +} + +#pragma mark - +#pragma mark ASSizeRange + +ASSizeRange ASSizeRangeMake(CGSize min, CGSize max) +{ + ASDisplayNodeCAssertPositiveReal(@"Range min width", min.width); + ASDisplayNodeCAssertPositiveReal(@"Range min height", min.height); + ASDisplayNodeCAssertInfOrPositiveReal(@"Range max width", max.width); + ASDisplayNodeCAssertInfOrPositiveReal(@"Range max height", max.height); + ASDisplayNodeCAssert(min.width <= max.width, + @"Range min width (%f) must not be larger than max width (%f).", min.width, max.width); + ASDisplayNodeCAssert(min.height <= max.height, + @"Range min height (%f) must not be larger than max height (%f).", min.height, max.height); + ASSizeRange sizeRange; sizeRange.min = min; sizeRange.max = max; return sizeRange; +} + +CGSize ASSizeRangeClamp(ASSizeRange sizeRange, CGSize size) +{ + return CGSizeMake(MAX(sizeRange.min.width, MIN(sizeRange.max.width, size.width)), + MAX(sizeRange.min.height, MIN(sizeRange.max.height, size.height))); +} + +struct _Range { + CGFloat min; + CGFloat max; + + /** + Intersects another dimension range. If the other range does not overlap, this size range "wins" by returning a + single point within its own range that is closest to the non-overlapping range. + */ + _Range intersect(const _Range &other) const + { + CGFloat newMin = MAX(min, other.min); + CGFloat newMax = MIN(max, other.max); + if (!(newMin > newMax)) { + return {newMin, newMax}; + } else { + // No intersection. If we're before the other range, return our max; otherwise our min. + if (min < other.min) { + return {max, max}; + } else { + return {min, min}; + } + } + } +}; + +ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRange) +{ + auto w = _Range({sizeRange.min.width, sizeRange.max.width}).intersect({otherSizeRange.min.width, otherSizeRange.max.width}); + auto h = _Range({sizeRange.min.height, sizeRange.max.height}).intersect({otherSizeRange.min.height, otherSizeRange.max.height}); + return {{w.min, h.min}, {w.max, h.max}}; +} + +BOOL ASSizeRangeEqualToSizeRange(ASSizeRange lhs, ASSizeRange rhs) +{ + return CGSizeEqualToSize(lhs.min, rhs.min) && CGSizeEqualToSize(lhs.max, rhs.max); +} + +NSString * NSStringFromASSizeRange(ASSizeRange sizeRange) +{ + return [NSString stringWithFormat:@"", + NSStringFromCGSize(sizeRange.min), + NSStringFromCGSize(sizeRange.max)]; +} diff --git a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h new file mode 100644 index 0000000000..7f1b0c9c00 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h @@ -0,0 +1,38 @@ +/* + * 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 + +/** + A layout spec that wraps another layoutable child, applying insets around it. + + If the child has a size specified as a percentage, the percentage is resolved against this spec's parent + size **after** applying insets. + + @example ASOuterLayoutSpec contains an ASInsetLayoutSpec with an ASInnerLayoutSpec. Suppose that: + - ASOuterLayoutSpec is 200pt wide. + - ASInnerLayoutSpec specifies its width as 100%. + - The ASInsetLayoutSpec has insets of 10pt on every side. + ASInnerLayoutSpec will have size 180pt, not 200pt, because it receives a parent size that has been adjusted for insets. + + If you're familiar with CSS: ASInsetLayoutSpec's child behaves similarly to "box-sizing: border-box". + + An infinite inset is resolved as an inset equal to all remaining space after applying the other insets and child size. + @example An ASInsetLayoutSpec with an infinite left inset and 10px for all other edges will position it's child 10px from the right edge. + */ +@interface ASInsetLayoutSpec : ASLayoutSpec + +/** + @param insets The amount of space to inset on each side. + @param child The wrapped child to inset. If nil, this method returns nil. + */ ++ (instancetype)newWithInsets:(UIEdgeInsets)insets child:(id)child; + +@end diff --git a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm new file mode 100644 index 0000000000..c5f3043107 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm @@ -0,0 +1,108 @@ +/* + * 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 "ASInsetLayoutSpec.h" + +#import "ASAssert.h" +#import "ASBaseDefines.h" + +#import "ASInternalHelpers.h" + +@interface ASInsetLayoutSpec () +{ + UIEdgeInsets _insets; + id _child; +} +@end + +/* Returns f if f is finite, substitute otherwise */ +static CGFloat finite(CGFloat f, CGFloat substitute) +{ + return isinf(f) ? substitute : f; +} + +/* Returns f if f is finite, 0 otherwise */ +static CGFloat finiteOrZero(CGFloat f) +{ + return finite(f, 0); +} + +/* Returns the inset required to center 'inner' in 'outer' */ +static CGFloat centerInset(CGFloat outer, CGFloat inner) +{ + return ASRoundPixelValue((outer - inner) / 2); +} + +@implementation ASInsetLayoutSpec + ++ (instancetype)newWithInsets:(UIEdgeInsets)insets child:(id)child +{ + if (child == nil) { + return nil; + } + ASInsetLayoutSpec *spec = [super new]; + if (spec) { + spec->_insets = insets; + spec->_child = child; + } + return spec; +} + ++ (instancetype)new +{ + ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); +} + +/** + Inset will compute a new constrained size for it's child after applying insets and re-positioning + the child to respect the inset. + */ +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + const CGFloat insetsX = (finiteOrZero(_insets.left) + finiteOrZero(_insets.right)); + const CGFloat insetsY = (finiteOrZero(_insets.top) + finiteOrZero(_insets.bottom)); + + // if either x-axis inset is infinite, let child be intrinsic width + const CGFloat minWidth = (isinf(_insets.left) || isinf(_insets.right)) ? 0 : constrainedSize.min.width; + // if either y-axis inset is infinite, let child be intrinsic height + const CGFloat minHeight = (isinf(_insets.top) || isinf(_insets.bottom)) ? 0 : constrainedSize.min.height; + + const ASSizeRange insetConstrainedSize = { + { + MAX(0, minWidth - insetsX), + MAX(0, minHeight - insetsY), + }, + { + MAX(0, constrainedSize.max.width - insetsX), + MAX(0, constrainedSize.max.height - insetsY), + } + }; + ASLayout *sublayout = [_child measureWithSizeRange:insetConstrainedSize]; + + const CGSize computedSize = ASSizeRangeClamp(constrainedSize, { + finite(sublayout.size.width + _insets.left + _insets.right, constrainedSize.max.width), + finite(sublayout.size.height + _insets.top + _insets.bottom, constrainedSize.max.height), + }); + + const CGFloat x = finite(_insets.left, constrainedSize.max.width - + (finite(_insets.right, + centerInset(constrainedSize.max.width, sublayout.size.width)) + sublayout.size.width)); + + const CGFloat y = finite(_insets.top, + constrainedSize.max.height - + (finite(_insets.bottom, + centerInset(constrainedSize.max.height, sublayout.size.height)) + sublayout.size.height)); + + sublayout.position = CGPointMake(x, y); + + return [ASLayout newWithLayoutableObject:self size:computedSize sublayouts:@[sublayout]]; +} + +@end diff --git a/AsyncDisplayKit/Layout/ASLayout.h b/AsyncDisplayKit/Layout/ASLayout.h new file mode 100644 index 0000000000..5bdd2190c8 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASLayout.h @@ -0,0 +1,65 @@ +/* + * 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 +#import +#import + +extern CGPoint const CGPointNull; + +extern BOOL CGPointIsNull(CGPoint point); + +/** Represents a computed immutable layout tree. */ +@interface ASLayout : NSObject + +@property (nonatomic, weak, readonly) id layoutableObject; +@property (nonatomic, readonly) CGSize size; +/** + * Position in parent. Default to CGPointNull. + * + * @discussion When being used as a sublayout, this property must not equal CGPointNull. + */ +@property (nonatomic, readwrite) CGPoint position; +/** + * Array of ASLayouts. Each must have a valid non-null position. + */ +@property (nonatomic, readonly) NSArray *sublayouts; + ++ (instancetype)newWithLayoutableObject:(id)layoutableObject + size:(CGSize)size + position:(CGPoint)position + sublayouts:(NSArray *)sublayouts; + +/** + * Convenience that has CGPointNull position. + */ ++ (instancetype)newWithLayoutableObject:(id)layoutableObject + size:(CGSize)size + sublayouts:(NSArray *)sublayouts; + +/** + * Convenience that has CGPointNull position and no sublayouts. + */ ++ (instancetype)newWithLayoutableObject:(id)layoutableObject size:(CGSize)size; + + +/** + * @abstract Evaluates a given predicate block against each object in the receiving layout tree + * and returns a new, 1-level deep layout containing the objects for which the predicate block returns true. + * + * @param predicateBlock The block is applied to a layout to be evaluated. + * The block takes 1 argument: evaluatedLayout - the layout to be evaluated. + * The block returns YES if evaluatedLayout evaluates to true, otherwise NO. + * + * @return A new, 1-level deep layout containing the layouts for which the predicate block returns true. + */ +- (ASLayout *)flattenedLayoutUsingPredicateBlock:(BOOL (^)(ASLayout *evaluatedLayout))predicateBlock; + +@end diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/AsyncDisplayKit/Layout/ASLayout.mm new file mode 100644 index 0000000000..31dd8ebc27 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASLayout.mm @@ -0,0 +1,94 @@ + /* + * 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 "ASLayout.h" +#import "ASAssert.h" +#import "ASLayoutSpecUtilities.h" +#import + +CGPoint const CGPointNull = {NAN, NAN}; + +extern BOOL CGPointIsNull(CGPoint point) +{ + return isnan(point.x) && isnan(point.y); +} + +@implementation ASLayout + ++ (instancetype)newWithLayoutableObject:(id)layoutableObject + size:(CGSize)size + position:(CGPoint)position + sublayouts:(NSArray *)sublayouts +{ + ASDisplayNodeAssert(layoutableObject, @"layoutableObject is required."); + for (ASLayout *sublayout in sublayouts) { + ASDisplayNodeAssert(!CGPointIsNull(sublayout.position), @"Invalid position is not allowed in sublayout."); + } + + ASLayout *l = [super new]; + if (l) { + l->_layoutableObject = layoutableObject; + l->_size = size; + l->_position = position; + l->_sublayouts = [sublayouts copy]; + } + return l; +} + ++ (instancetype)newWithLayoutableObject:(id)layoutableObject + size:(CGSize)size + sublayouts:(NSArray *)sublayouts +{ + return [self newWithLayoutableObject:layoutableObject size:size position:CGPointNull sublayouts:sublayouts]; +} + ++ (instancetype)newWithLayoutableObject:(id)layoutableObject size:(CGSize)size +{ + return [self newWithLayoutableObject:layoutableObject size:size sublayouts:nil]; +} + +- (ASLayout *)flattenedLayoutUsingPredicateBlock:(BOOL (^)(ASLayout *))predicateBlock +{ + NSMutableArray *flattenedSublayouts = [NSMutableArray array]; + + struct Context { + ASLayout *layout; + CGPoint absolutePosition; + BOOL visited; + }; + + // Stack of Contexts, used to keep track of sublayouts while traversing this layout in a DFS fashion. + std::stack stack; + stack.push({self, CGPointMake(0, 0), NO}); + + while (!stack.empty()) { + Context &context = stack.top(); + if (context.visited) { + stack.pop(); + } else { + context.visited = YES; + + if (predicateBlock(context.layout)) { + [flattenedSublayouts addObject:[ASLayout newWithLayoutableObject:context.layout.layoutableObject + size:context.layout.size + position:context.absolutePosition + sublayouts:nil]]; + } + + for (ASLayout *sublayout in context.layout.sublayouts) { + stack.push({sublayout, context.absolutePosition + sublayout.position, NO}); + } + } + } + + return [ASLayout newWithLayoutableObject:_layoutableObject size:_size sublayouts:flattenedSublayouts]; +} + +@end diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.h b/AsyncDisplayKit/Layout/ASLayoutSpec.h new file mode 100644 index 0000000000..122033d091 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.h @@ -0,0 +1,17 @@ +/* + * 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 +#import + +/** A layout spec is an immutable object that describes a layout, loosely inspired by React. */ +@interface ASLayoutSpec : NSObject + +@end diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.mm b/AsyncDisplayKit/Layout/ASLayoutSpec.mm new file mode 100644 index 0000000000..8b3efd6f4d --- /dev/null +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.mm @@ -0,0 +1,44 @@ +/* + * 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 "ASLayoutSpec.h" + +#import "ASAssert.h" +#import "ASBaseDefines.h" + +#import "ASInternalHelpers.h" +#import "ASLayout.h" + +@implementation ASLayoutSpec + +@synthesize spacingBefore = _spacingBefore; +@synthesize spacingAfter = _spacingAfter; +@synthesize flexGrow = _flexGrow; +@synthesize flexShrink = _flexShrink; +@synthesize flexBasis = _flexBasis; +@synthesize alignSelf = _alignSelf; + ++ (instancetype)new +{ + ASLayoutSpec *spec = [super new]; + if (spec) { + spec->_flexBasis = ASRelativeDimensionUnconstrained; + } + return spec; +} + +#pragma mark - Layout + +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + return [ASLayout newWithLayoutableObject:self size:constrainedSize.min]; +} + +@end diff --git a/AsyncDisplayKit/Layout/ASLayoutable.h b/AsyncDisplayKit/Layout/ASLayoutable.h new file mode 100644 index 0000000000..5fc091b12f --- /dev/null +++ b/AsyncDisplayKit/Layout/ASLayoutable.h @@ -0,0 +1,64 @@ +/* + * 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 +#import + +@class ASLayout; + +@protocol ASLayoutable + +/** + * @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 Calculate a layout based on given size range. + * + * @param constrainedSize The minimum and maximum sizes the receiver should fit in. + * + * @return An ASLayout instance defining the layout of the receiver and its children. + */ +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize; + +@end diff --git a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h new file mode 100644 index 0000000000..846b0cece5 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h @@ -0,0 +1,20 @@ +/* + * 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 + +/** + This layout spec lays out a single layoutable child and then overlays a layoutable object on top of it streched to its size + */ +@interface ASOverlayLayoutSpec : ASLayoutSpec + ++ (instancetype)newWithChild:(id)child overlay:(id)overlay; + +@end diff --git a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm new file mode 100644 index 0000000000..074753f143 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm @@ -0,0 +1,55 @@ +/* + * 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 "ASOverlayLayoutSpec.h" + +#import "ASAssert.h" +#import "ASBaseDefines.h" + +@implementation ASOverlayLayoutSpec +{ + id _overlay; + id _child; +} + ++ (instancetype)newWithChild:(id)child overlay:(id)overlay +{ + ASOverlayLayoutSpec *spec = [super new]; + if (spec) { + ASDisplayNodeAssertNotNil(child, @"Child that will be overlayed on shouldn't be nil"); + spec->_overlay = overlay; + spec->_child = child; + } + return spec; +} + ++ (instancetype)new +{ + ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); +} + +/** + First layout the contents, then fit the overlay on top of it. + */ +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + ASLayout *contentsLayout = [_child measureWithSizeRange:constrainedSize]; + contentsLayout.position = CGPointZero; + NSMutableArray *sublayouts = [NSMutableArray arrayWithObject:contentsLayout]; + if (_overlay) { + ASLayout *overlayLayout = [_overlay measureWithSizeRange:{contentsLayout.size, contentsLayout.size}]; + overlayLayout.position = CGPointZero; + [sublayouts addObject:overlayLayout]; + } + + return [ASLayout newWithLayoutableObject:self size:contentsLayout.size sublayouts:sublayouts]; +} + +@end diff --git a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.h b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.h new file mode 100644 index 0000000000..73be620d71 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.h @@ -0,0 +1,36 @@ +/* + * 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 +#import + +/** + Ratio layout spec + For when the content should respect a certain inherent ratio but can be scaled (think photos or videos) + The ratio passed is the ratio of height / width you expect + + For a ratio 0.5, the spec will have a flat rectangle shape + _ _ _ _ + | | + |_ _ _ _| + + For a ratio 2.0, the spec will be twice as tall as it is wide + _ _ + | | + | | + | | + |_ _| + + **/ +@interface ASRatioLayoutSpec : ASLayoutSpec + ++ (instancetype)newWithRatio:(CGFloat)ratio child:(id)child; + +@end diff --git a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm new file mode 100644 index 0000000000..103be815a7 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm @@ -0,0 +1,75 @@ +/* + * 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 "ASRatioLayoutSpec.h" + +#import +#import + +#import "ASAssert.h" +#import "ASBaseDefines.h" + +#import "ASInternalHelpers.h" + +@implementation ASRatioLayoutSpec +{ + CGFloat _ratio; + id _child; +} + ++ (instancetype)newWithRatio:(CGFloat)ratio child:(id)child +{ + ASDisplayNodeAssert(ratio > 0, @"Ratio should be strictly positive, but received %f", ratio); + if (child == nil) { + return nil; + } + + ASRatioLayoutSpec *spec = [super new]; + if (spec) { + spec->_ratio = ratio; + spec->_child = child; + } + return spec; +} + ++ (instancetype)new +{ + ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); +} + +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + std::vector sizeOptions; + if (!isinf(constrainedSize.max.width)) { + sizeOptions.push_back(ASSizeRangeClamp(constrainedSize, { + constrainedSize.max.width, + ASFloorPixelValue(_ratio * constrainedSize.max.width) + })); + } + if (!isinf(constrainedSize.max.height)) { + sizeOptions.push_back(ASSizeRangeClamp(constrainedSize, { + ASFloorPixelValue(constrainedSize.max.height / _ratio), + constrainedSize.max.height + })); + } + + // Choose the size closest to the desired ratio. + const auto &bestSize = std::max_element(sizeOptions.begin(), sizeOptions.end(), [&](const CGSize &a, const CGSize &b){ + return fabs((a.height / a.width) - _ratio) > fabs((b.height / b.width) - _ratio); + }); + + // 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]; + sublayout.position = CGPointZero; + return [ASLayout newWithLayoutableObject:self size:sublayout.size sublayouts:@[sublayout]]; +} + +@end diff --git a/AsyncDisplayKit/Layout/ASStackLayoutChild.h b/AsyncDisplayKit/Layout/ASStackLayoutChild.h new file mode 100644 index 0000000000..040d8ac01a --- /dev/null +++ b/AsyncDisplayKit/Layout/ASStackLayoutChild.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +/** + Each child may override their parent stack's cross axis alignment. + @see ASStackLayoutAlignItems + */ +typedef NS_ENUM(NSUInteger, ASStackLayoutAlignSelf) { + /** Inherit alignment value from containing stack. */ + ASStackLayoutAlignSelfAuto, + ASStackLayoutAlignSelfStart, + ASStackLayoutAlignSelfEnd, + ASStackLayoutAlignSelfCenter, + ASStackLayoutAlignSelfStretch, +}; diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h new file mode 100644 index 0000000000..83322b8de5 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h @@ -0,0 +1,87 @@ +/* + * 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 + +typedef NS_ENUM(NSUInteger, ASStackLayoutDirection) { + ASStackLayoutDirectionVertical, + ASStackLayoutDirectionHorizontal, +}; + +/** If no children are flexible, how should this spec justify its children in the available space? */ +typedef NS_ENUM(NSUInteger, ASStackLayoutJustifyContent) { + /** + On overflow, children overflow out of this spec's bounds on the right/bottom side. + On underflow, children are left/top-aligned within this spec's bounds. + */ + ASStackLayoutJustifyContentStart, + /** + On overflow, children are centered and overflow on both sides. + On underflow, children are centered within this spec's bounds in the stacking direction. + */ + ASStackLayoutJustifyContentCenter, + /** + On overflow, children overflow out of this spec's bounds on the left/top side. + On underflow, children are right/bottom-aligned within this spec's bounds. + */ + ASStackLayoutJustifyContentEnd, +}; + +typedef NS_ENUM(NSUInteger, ASStackLayoutAlignItems) { + /** Align children to start of cross axis */ + ASStackLayoutAlignItemsStart, + /** Align children with end of cross axis */ + ASStackLayoutAlignItemsEnd, + /** Center children on cross axis */ + ASStackLayoutAlignItemsCenter, + /** Expand children to fill cross axis */ + ASStackLayoutAlignItemsStretch, +}; + +typedef struct { + /** Specifies the direction children are stacked in. */ + ASStackLayoutDirection direction; + /** The amount of space between each child. */ + CGFloat spacing; + /** How children are aligned if there are no flexible children. */ + ASStackLayoutJustifyContent justifyContent; + /** Orientation of children along cross axis */ + ASStackLayoutAlignItems alignItems; +} ASStackLayoutSpecStyle; + +/** + A simple layout spec that stacks a list of children vertically or horizontally. + + - All children are initially laid out with the an infinite available size in the stacking direction. + - In the other direction, this spec's constraint is passed. + - The children's sizes are summed in the stacking direction. + - If this sum is less than this spec's minimum size in stacking direction, children with flexGrow are flexed. + - If it is greater than this spec's maximum size in the stacking direction, children with flexShrink are flexed. + - If, even after flexing, the sum is still greater than this spec's maximum size in the stacking direction, + justifyContent determines how children are laid out. + + For example: + - Suppose stacking direction is Vertical, min-width=100, max-width=300, min-height=200, max-height=500. + - All children are laid out with min-width=100, max-width=300, min-height=0, max-height=INFINITY. + - If the sum of the childrens' heights is less than 200, children with flexGrow are flexed larger. + - If the sum of the childrens' heights is greater than 500, children with flexShrink are flexed smaller. + Each child is shrunk by `((sum of heights) - 500)/(number of flexShrink-able children)`. + - If the sum of the childrens' heights is greater than 500 even after flexShrink-able children are flexed, + justifyContent determines how children are laid out. + */ +@interface ASStackLayoutSpec : ASLayoutSpec + +/** + @param style Specifies how children are laid out. + @param children ASLayoutable children to be positioned. + */ ++ (instancetype)newWithStyle:(ASStackLayoutSpecStyle)style children:(NSArray *)children; + +@end diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm new file mode 100644 index 0000000000..75b699c55b --- /dev/null +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import "ASStackLayoutSpec.h" + +#import +#import + +#import "ASBaseDefines.h" +#import "ASInternalHelpers.h" + +#import "ASLayoutSpecUtilities.h" +#import "ASStackLayoutSpecUtilities.h" +#import "ASStackPositionedLayout.h" +#import "ASStackUnpositionedLayout.h" + +@implementation ASStackLayoutSpec +{ + ASStackLayoutSpecStyle _style; + std::vector> _children; +} + ++ (instancetype)newWithStyle:(ASStackLayoutSpecStyle)style children:(NSArray *)children +{ + ASStackLayoutSpec *spec = [super new]; + if (spec) { + spec->_style = style; + spec->_children = std::vector>(); + for (id child in children) { + spec->_children.push_back(child); + } + } + return spec; +} + ++ (instancetype)new +{ + ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); +} + +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + const auto unpositionedLayout = ASStackUnpositionedLayout::compute(_children, _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()]; + return [ASLayout newWithLayoutableObject:self + size:ASSizeRangeClamp(constrainedSize, finalSize) + sublayouts:sublayouts]; +} + +@end diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.h b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.h new file mode 100644 index 0000000000..91497c2da2 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.h @@ -0,0 +1,46 @@ +/* + * 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 +#import + +@interface ASStaticLayoutSpecChild : NSObject + +@property (nonatomic, readonly) CGPoint position; +@property (nonatomic, readonly) id 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; + ++ (instancetype)newWithPosition:(CGPoint)position layoutableObject:(id)layoutableObject size:(ASRelativeSizeRange)size; + +/** + Convenience 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. + */ ++ (instancetype)newWithPosition:(CGPoint)position layoutableObject:(id)layoutableObject; + +@end + +/* + A layout spec that positions children at fixed positions. + + Computes a size that is the union of all childrens' frames. + */ +@interface ASStaticLayoutSpec : ASLayoutSpec + +/** + @param children Children to be positioned at fixed positions, each is of type ASStaticLayoutSpecChild. + */ ++ (instancetype)newWithChildren:(NSArray *)children; + +@end diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm new file mode 100644 index 0000000000..f7488a7133 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm @@ -0,0 +1,95 @@ +/* + * 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 "ASStaticLayoutSpec.h" + +#import "ASLayoutSpecUtilities.h" +#import "ASInternalHelpers.h" + +@implementation ASStaticLayoutSpecChild + ++ (instancetype)newWithPosition:(CGPoint)position layoutableObject:(id)layoutableObject size:(ASRelativeSizeRange)size +{ + ASStaticLayoutSpecChild *c = [super new]; + if (c) { + c->_position = position; + c->_layoutableObject = layoutableObject; + c->_size = size; + } + return c; +} + ++ (instancetype)newWithPosition:(CGPoint)position layoutableObject:(id)layoutableObject +{ + return [self newWithPosition:position layoutableObject:layoutableObject size:ASRelativeSizeRangeUnconstrained]; +} + +@end + +@implementation ASStaticLayoutSpec +{ + NSArray *_children; +} + ++ (instancetype)newWithChildren:(NSArray *)children +{ + ASStaticLayoutSpec *spec = [super new]; + if (spec) { + spec->_children = children; + } + return spec; +} + ++ (instancetype)new +{ + ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); +} + +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + CGSize size = { + constrainedSize.max.width, + constrainedSize.max.height + }; + + NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:_children.count]; + for (ASStaticLayoutSpecChild *child in _children) { + CGSize autoMaxSize = { + constrainedSize.max.width - child.position.x, + constrainedSize.max.height - child.position.y + }; + ASSizeRange childConstraint = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, child.size) + ? ASSizeRangeMake({0, 0}, autoMaxSize) + : ASRelativeSizeRangeResolve(child.size, size); + ASLayout *sublayout = [child.layoutableObject measureWithSizeRange:childConstraint]; + sublayout.position = child.position; + [sublayouts addObject:sublayout]; + } + + if (isnan(size.width)) { + size.width = constrainedSize.min.width; + for (ASLayout *sublayout in sublayouts) { + size.width = MAX(size.width, sublayout.position.x + sublayout.size.width); + } + } + + if (isnan(size.height)) { + size.height = constrainedSize.min.height; + for (ASLayout *sublayout in sublayouts) { + size.height = MAX(size.height, sublayout.position.y + sublayout.size.height); + } + } + + return [ASLayout newWithLayoutableObject:self + size:ASSizeRangeClamp(constrainedSize, size) + sublayouts:sublayouts]; +} + +@end diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpecDimension.h b/AsyncDisplayKit/Layout/ASStaticLayoutSpecDimension.h new file mode 100644 index 0000000000..9522d43de3 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpecDimension.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import +#import +#import + +/** + Expresses a size with relative dimensions. + Used by ASStaticLayoutSpec. + */ +typedef struct { + ASRelativeDimension width; + ASRelativeDimension height; +} ASRelativeSize; + +/** + Expresses an inclusive range of relative sizes. Used to provide additional constraint to layout. + Used by ASStaticLayoutSpec. + */ +typedef struct { + ASRelativeSize min; + ASRelativeSize max; +} ASRelativeSizeRange; + +extern ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained; + +ASDISPLAYNODE_EXTERN_C_BEGIN + +#pragma mark - +#pragma mark ASRelativeSize + +extern ASRelativeSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height); + +/** Convenience constructor to provide size in Points. */ +extern ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size); + +/** Resolve this relative size relative to a parent size. */ +extern CGSize ASRelativeSizeResolve(ASRelativeSize relativeSize, CGSize parentSize); + +extern BOOL ASRelativeSizeEqualToRelativeSize(ASRelativeSize lhs, ASRelativeSize rhs); + +extern NSString *NSStringFromASRelativeSize(ASRelativeSize size); + +#pragma mark - +#pragma mark ASRelativeSizeRange + +extern ASRelativeSizeRange ASRelativeSizeRangeMake(ASRelativeSize min, ASRelativeSize max); + +#pragma mark Convenience constructors to provide an exact size (min == max). +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSize exact); + +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact); + +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, + ASRelativeDimension exactHeight); + +extern BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs); + +/** + Provided a parent size, compute final dimensions for this RelativeSizeRange to arrive at a SizeRange. + */ +extern ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, + CGSize parentSize); + +ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpecDimension.mm b/AsyncDisplayKit/Layout/ASStaticLayoutSpecDimension.mm new file mode 100644 index 0000000000..4790cd1582 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpecDimension.mm @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import "ASStaticLayoutSpecDimension.h" +#import "ASAssert.h" + +ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained = {}; + +#pragma mark - +#pragma mark ASRelativeSize + +ASRelativeSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) +{ + ASRelativeSize size; size.width = width; size.height = height; return size; +} + +ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size) +{ + return ASRelativeSizeMake(ASRelativeDimensionMakeWithPoints(size.width), + ASRelativeDimensionMakeWithPoints(size.height)); +} + +CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize) +{ + return CGSizeMake(ASRelativeDimensionResolve(relativeSize.width, parentSize.width), + ASRelativeDimensionResolve(relativeSize.height, parentSize.height)); +} + +BOOL ASRelativeSizeEqualToRelativeSize(ASRelativeSize lhs, ASRelativeSize rhs) +{ + return ASRelativeDimensionEqualToRelativeDimension(lhs.width, rhs.width) + && ASRelativeDimensionEqualToRelativeDimension(lhs.height, rhs.height); +} + +NSString *NSStringFromASRelativeSize(ASRelativeSize size) +{ + return [NSString stringWithFormat:@"{%@, %@}", + NSStringFromASRelativeDimension(size.width), + NSStringFromASRelativeDimension(size.height)]; +} + +#pragma mark - +#pragma mark ASRelativeSizeRange + +ASRelativeSizeRange ASRelativeSizeRangeMake(ASRelativeSize min, ASRelativeSize max) +{ + ASRelativeSizeRange sizeRange; sizeRange.min = min; sizeRange.max = max; return sizeRange; +} + +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSize exact) +{ + return ASRelativeSizeRangeMake(exact, exact); +} + +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) +{ + return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithCGSize(exact)); +} + +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, + ASRelativeDimension exactHeight) +{ + return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMake(exactWidth, exactHeight)); +} + +BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs) +{ + return ASRelativeSizeEqualToRelativeSize(lhs.min, rhs.min) && ASRelativeSizeEqualToRelativeSize(lhs.max, rhs.max); +} + +ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, + CGSize parentSize) +{ + return ASSizeRangeMake(ASRelativeSizeResolveSize(relativeSizeRange.min, parentSize), + ASRelativeSizeResolveSize(relativeSizeRange.max, parentSize)); +} diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index 9fb21dad84..99167a93d4 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -87,15 +87,23 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, // Capture these outside the display block so they are retained. UIColor *backgroundColor = self.backgroundColor; CGRect bounds = self.bounds; - CGPoint position = self.position; - CGPoint anchorPoint = self.anchorPoint; - // Pretty hacky since full 3D transforms aren't actually supported, but attempt to compute the transformed frame of this node so that we can composite it into approximately the right spot. - CGAffineTransform transform = CATransform3DGetAffineTransform(self.transform); - CGSize scaledBoundsSize = CGSizeApplyAffineTransform(bounds.size, transform); - CGPoint origin = CGPointMake(position.x - scaledBoundsSize.width * anchorPoint.x, - position.y - scaledBoundsSize.height * anchorPoint.y); - CGRect frame = CGRectMake(origin.x, origin.y, bounds.size.width, bounds.size.height); + CGRect frame; + + // If this is the root container node, use a frame with a zero origin to draw into. If not, calculate the correct frame using the node's position, transform and anchorPoint. + if (self.shouldRasterizeDescendants) { + frame = CGRectMake(0.0f, 0.0f, bounds.size.width, bounds.size.height); + } else { + CGPoint position = self.position; + CGPoint anchorPoint = self.anchorPoint; + + // Pretty hacky since full 3D transforms aren't actually supported, but attempt to compute the transformed frame of this node so that we can composite it into approximately the right spot. + CGAffineTransform transform = CATransform3DGetAffineTransform(self.transform); + CGSize scaledBoundsSize = CGSizeApplyAffineTransform(bounds.size, transform); + CGPoint origin = CGPointMake(position.x - scaledBoundsSize.width * anchorPoint.x, + position.y - scaledBoundsSize.height * anchorPoint.y); + frame = CGRectMake(origin.x, origin.y, bounds.size.width, bounds.size.height); + } // Get the display block for this node. asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:NO isCancelledBlock:isCancelledBlock rasterizing:YES]; @@ -165,7 +173,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, [self _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks]; CGFloat contentsScaleForDisplay = self.contentsScaleForDisplay; - BOOL opaque = self.opaque; + BOOL opaque = self.opaque && CGColorGetAlpha(self.backgroundColor.CGColor) == 1.0f; ASDisplayNodeAssert(self.contentsScaleForDisplay != 0.0, @"Invalid contents scale"); @@ -177,7 +185,6 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, } ASDN_DELAY_FOR_DISPLAY(); - UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); for (dispatch_block_t block in displayBlocks) { diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 51f865dffd..2f2994fd98 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -143,16 +143,21 @@ ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"Must be an identity transform"); #endif - if (_layer && ASDisplayNodeThreadIsMain()) { - CGPoint anchorPoint = _layer.anchorPoint; - _layer.bounds = CGRectMake(0, 0, rect.size.width, rect.size.height); - _layer.position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x, - rect.origin.y + rect.size.height * anchorPoint.y); + BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain()); + + CGPoint origin = (useLayer ? _layer.bounds.origin : self.bounds.origin); + CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint); + + CGRect bounds = (CGRect){ origin, rect.size }; + CGPoint position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x, + rect.origin.y + rect.size.height * anchorPoint.y); + + if (useLayer) { + _layer.bounds = bounds; + _layer.position = position; } else { - CGPoint anchorPoint = self.anchorPoint; - self.bounds = CGRectMake(0, 0, rect.size.width, rect.size.height); - self.position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x, - rect.origin.y + rect.size.height * anchorPoint.y); + self.bounds = bounds; + self.position = position; } } @@ -637,6 +642,18 @@ _setToViewOnly(shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren); } +- (NSString *)accessibilityIdentifier +{ + _bridge_prologue; + return _getFromViewOnly(accessibilityIdentifier); +} + +- (void)setAccessibilityIdentifier:(NSString *)accessibilityIdentifier +{ + _bridge_prologue; + _setToViewOnly(accessibilityIdentifier, accessibilityIdentifier); +} + @end diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 4988d60211..cded26c2c6 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -17,17 +17,18 @@ #import "ASDisplayNode.h" #import "ASSentinel.h" #import "ASThread.h" +#import "ASLayout.h" BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); -CGFloat ASDisplayNodeScreenScale(); void ASDisplayNodePerformBlockOnMainThread(void (^block)()); typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { ASDisplayNodeMethodOverrideNone = 0, - ASDisplayNodeMethodOverrideTouchesBegan = 1 << 0, - ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1, - ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2, - ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3 + ASDisplayNodeMethodOverrideTouchesBegan = 1 << 0, + ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1, + ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2, + ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3, + ASDisplayNodeMethodOverrideCalculateSizeThatFits = 1 << 4 }; @class _ASPendingState; @@ -51,8 +52,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { // This is the desired contentsScale, not the scale at which the layer's contents should be displayed CGFloat _contentsScaleForDisplay; - CGSize _size; - CGSize _constrainedSize; + ASLayout *_layout; + ASSizeRange _constrainedSize; UIEdgeInsets _hitTestSlop; NSMutableArray *_subnodes; @@ -118,8 +119,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { - (BOOL)__shouldSize; - (void)__exitedHierarchy; -// Core implementation of -measure:. Must be called with _propertyLock held. -- (CGSize)__measure:(CGSize)constrainedSize; +// Core implementation of -measureWithSizeRange:. Must be called with _propertyLock held. +- (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize; - (void)__layout; - (void)__setSupernode:(ASDisplayNode *)supernode; diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h new file mode 100644 index 0000000000..4349eb011b --- /dev/null +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -0,0 +1,28 @@ +/* + * 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 +#import "ASBaseDefines.h" + +@class ASLayoutChild; + +ASDISPLAYNODE_EXTERN_C_BEGIN + +BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector); + +CGFloat ASScreenScale(); + +CGFloat ASFloorPixelValue(CGFloat f); + +CGFloat ASCeilPixelValue(CGFloat f); + +CGFloat ASRoundPixelValue(CGFloat f); + +ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.mm b/AsyncDisplayKit/Private/ASInternalHelpers.mm new file mode 100644 index 0000000000..d161523f2b --- /dev/null +++ b/AsyncDisplayKit/Private/ASInternalHelpers.mm @@ -0,0 +1,63 @@ +/* + * 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 "ASInternalHelpers.h" + +#import +#import + +#import "ASLayout.h" + +BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector) +{ + Method superclassMethod = class_getInstanceMethod(superclass, selector); + Method subclassMethod = class_getInstanceMethod(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]) { + dispatch_once(predicate, block); + } else { + if (DISPATCH_EXPECT(*predicate == 0L, NO)) { + dispatch_sync(dispatch_get_main_queue(), ^{ + dispatch_once(predicate, block); + }); + } + } +} + +CGFloat ASScreenScale() +{ + static CGFloat _scale; + static dispatch_once_t onceToken; + ASDispatchOnceOnMainThread(&onceToken, ^{ + _scale = [UIScreen mainScreen].scale; + }); + return _scale; +} + +CGFloat ASFloorPixelValue(CGFloat f) +{ + return floorf(f * ASScreenScale()) / ASScreenScale(); +} + +CGFloat ASCeilPixelValue(CGFloat f) +{ + return ceilf(f * ASScreenScale()) / ASScreenScale(); +} + +CGFloat ASRoundPixelValue(CGFloat f) +{ + return roundf(f * ASScreenScale()) / ASScreenScale(); +} diff --git a/AsyncDisplayKit/Private/ASLayoutSpecUtilities.h b/AsyncDisplayKit/Private/ASLayoutSpecUtilities.h new file mode 100644 index 0000000000..178b9655c0 --- /dev/null +++ b/AsyncDisplayKit/Private/ASLayoutSpecUtilities.h @@ -0,0 +1,105 @@ +/* + * 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 +#import +#import +#import + +#import + +namespace AS { + // adopted from http://stackoverflow.com/questions/14945223/map-function-with-c11-constructs + // Takes an iterable, applies a function to every element, + // and returns a vector of the results + // + template + auto map(const T &iterable, Func &&func) -> std::vector()))> + { + // Some convenience type definitions + typedef decltype(func(std::declval())) value_type; + typedef std::vector result_type; + + // Prepares an output vector of the appropriate size + result_type res(iterable.size()); + + // Let std::transform apply `func` to all elements + // (use perfect forwarding for the function object) + std::transform( + begin(iterable), end(iterable), res.begin(), + std::forward(func) + ); + + return res; + } + + template + auto map(id collection, Func &&func) -> std::vector()))> + { + std::vector()))> to; + for (id obj in collection) { + to.push_back(func(obj)); + } + return to; + } + + template + auto filter(const T &iterable, Func &&func) -> std::vector + { + std::vector to; + for (auto obj : iterable) { + if (func(obj)) { + to.push_back(obj); + } + } + return to; + } +}; + +inline CGPoint operator+(const CGPoint &p1, const CGPoint &p2) +{ + return { p1.x + p2.x, p1.y + p2.y }; +} + +inline CGPoint operator-(const CGPoint &p1, const CGPoint &p2) +{ + return { p1.x - p2.x, p1.y - p2.y }; +} + +inline CGSize operator+(const CGSize &s1, const CGSize &s2) +{ + return { s1.width + s2.width, s1.height + s2.height }; +} + +inline CGSize operator-(const CGSize &s1, const CGSize &s2) +{ + return { s1.width - s2.width, s1.height - s2.height }; +} + +inline UIEdgeInsets operator+(const UIEdgeInsets &e1, const UIEdgeInsets &e2) +{ + return { e1.top + e2.top, e1.left + e2.left, e1.bottom + e2.bottom, e1.right + e2.right }; +} + +inline UIEdgeInsets operator-(const UIEdgeInsets &e1, const UIEdgeInsets &e2) +{ + return { e1.top - e2.top, e1.left - e2.left, e1.bottom - e2.bottom, e1.right - e2.right }; +} + +inline UIEdgeInsets operator*(const UIEdgeInsets &e1, const UIEdgeInsets &e2) +{ + return { e1.top * e2.top, e1.left * e2.left, e1.bottom * e2.bottom, e1.right * e2.right }; +} + +inline UIEdgeInsets operator-(const UIEdgeInsets &e) +{ + return { -e.top, -e.left, -e.bottom, -e.right }; +} + diff --git a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h new file mode 100644 index 0000000000..4f5eff0aeb --- /dev/null +++ b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h @@ -0,0 +1,62 @@ +/* + * 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 "ASStackLayoutSpec.h" + +inline CGFloat stackDimension(const ASStackLayoutDirection direction, const CGSize size) +{ + return (direction == ASStackLayoutDirectionVertical) ? size.height : size.width; +} + +inline CGFloat crossDimension(const ASStackLayoutDirection direction, const CGSize size) +{ + return (direction == ASStackLayoutDirectionVertical) ? size.width : size.height; +} + +inline BOOL compareCrossDimension(const ASStackLayoutDirection direction, const CGSize a, const CGSize b) +{ + return crossDimension(direction, a) < crossDimension(direction, b); +} + +inline CGPoint directionPoint(const ASStackLayoutDirection direction, const CGFloat stack, const CGFloat cross) +{ + return (direction == ASStackLayoutDirectionVertical) ? CGPointMake(cross, stack) : CGPointMake(stack, cross); +} + +inline CGSize directionSize(const ASStackLayoutDirection direction, const CGFloat stack, const CGFloat cross) +{ + return (direction == ASStackLayoutDirectionVertical) ? CGSizeMake(cross, stack) : CGSizeMake(stack, cross); +} + +inline ASSizeRange directionSizeRange(const ASStackLayoutDirection direction, + const CGFloat stackMin, + const CGFloat stackMax, + const CGFloat crossMin, + const CGFloat crossMax) +{ + return {directionSize(direction, stackMin, crossMin), directionSize(direction, stackMax, crossMax)}; +} + +inline ASStackLayoutAlignItems alignment(ASStackLayoutAlignSelf childAlignment, ASStackLayoutAlignItems stackAlignment) +{ + switch (childAlignment) { + case ASStackLayoutAlignSelfCenter: + return ASStackLayoutAlignItemsCenter; + case ASStackLayoutAlignSelfEnd: + return ASStackLayoutAlignItemsEnd; + case ASStackLayoutAlignSelfStart: + return ASStackLayoutAlignItemsStart; + case ASStackLayoutAlignSelfStretch: + return ASStackLayoutAlignItemsStretch; + case ASStackLayoutAlignSelfAuto: + default: + return stackAlignment; + } +} diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.h b/AsyncDisplayKit/Private/ASStackPositionedLayout.h new file mode 100644 index 0000000000..211bda5b11 --- /dev/null +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.h @@ -0,0 +1,25 @@ +/* + * 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 "ASLayout.h" +#import "ASDimension.h" +#import "ASStackLayoutSpec.h" +#import "ASStackUnpositionedLayout.h" + +/** Represents a set of laid out and positioned stack layout children. */ +struct ASStackPositionedLayout { + const std::vector sublayouts; + const CGFloat crossSize; + + /** Given an unpositioned layout, computes the positions each child should be placed at. */ + static ASStackPositionedLayout compute(const ASStackUnpositionedLayout &unpositionedLayout, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &constrainedSize); +}; diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm new file mode 100644 index 0000000000..420258460f --- /dev/null +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -0,0 +1,75 @@ +/* + * 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 "ASStackPositionedLayout.h" + +#import "ASInternalHelpers.h" +#import "ASLayoutSpecUtilities.h" +#import "ASStackLayoutSpecUtilities.h" +#import "ASLayoutable.h" + +static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, + const ASStackUnpositionedItem &l, + const CGFloat crossSize) +{ + switch (alignment(l.child.alignSelf, style.alignItems)) { + case ASStackLayoutAlignItemsEnd: + return crossSize - crossDimension(style.direction, l.layout.size); + case ASStackLayoutAlignItemsCenter: + return ASFloorPixelValue((crossSize - crossDimension(style.direction, l.layout.size)) / 2); + case ASStackLayoutAlignItemsStart: + case ASStackLayoutAlignItemsStretch: + return 0; + } +} + +static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style, + const CGFloat offset, + const ASStackUnpositionedLayout &unpositionedLayout, + const ASSizeRange &constrainedSize) +{ + // The cross dimension is the max of the childrens' cross dimensions (clamped to our constraint below). + const auto it = std::max_element(unpositionedLayout.items.begin(), unpositionedLayout.items.end(), + [&](const ASStackUnpositionedItem &a, const ASStackUnpositionedItem &b){ + return compareCrossDimension(style.direction, a.layout.size, b.layout.size); + }); + const auto largestChildCrossSize = it == unpositionedLayout.items.end() ? 0 : crossDimension(style.direction, it->layout.size); + const auto minCrossSize = crossDimension(style.direction, constrainedSize.min); + const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max); + const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize); + + CGPoint p = directionPoint(style.direction, offset, 0); + BOOL first = YES; + auto stackedChildren = AS::map(unpositionedLayout.items, [&](const ASStackUnpositionedItem &l) -> ASLayout *{ + p = p + directionPoint(style.direction, l.child.spacingBefore, 0); + if (!first) { + p = p + directionPoint(style.direction, style.spacing, 0); + } + first = NO; + l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize)); + p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.spacingAfter, 0); + return l.layout; + }); + return {stackedChildren, crossSize}; +} + +ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &constrainedSize) +{ + switch (style.justifyContent) { + case ASStackLayoutJustifyContentStart: + return stackedLayout(style, 0, unpositionedLayout, constrainedSize); + case ASStackLayoutJustifyContentCenter: + return stackedLayout(style, floorf(unpositionedLayout.violation / 2), unpositionedLayout, constrainedSize); + case ASStackLayoutJustifyContentEnd: + return stackedLayout(style, unpositionedLayout.violation, unpositionedLayout, constrainedSize); + } +} diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h new file mode 100644 index 0000000000..45f648192e --- /dev/null +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h @@ -0,0 +1,36 @@ +/* + * 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 + +#import "ASLayout.h" +#import "ASStackLayoutSpec.h" + +struct ASStackUnpositionedItem { + /** The original source child. */ + id child; + /** The proposed layout. */ + ASLayout *layout; +}; + +/** Represents a set of stack layout children that have their final layout computed, but are not yet positioned. */ +struct ASStackUnpositionedLayout { + /** A set of proposed child layouts, not yet positioned. */ + const std::vector items; + /** The total size of the children in the stack dimension, including all spacing. */ + const CGFloat stackDimensionSum; + /** The amount by which stackDimensionSum violates constraints. If positive, less than min; negative, greater than max. */ + const CGFloat violation; + + /** Given a set of children, computes the unpositioned layouts for those children. */ + static ASStackUnpositionedLayout compute(const std::vector> &children, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange); +}; diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm new file mode 100644 index 0000000000..9eb83a5b0d --- /dev/null +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm @@ -0,0 +1,341 @@ +/* + * 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 "ASStackUnpositionedLayout.h" + +#import + +#import "ASLayoutSpecUtilities.h" +#import "ASStackLayoutSpecUtilities.h" + +/** + Sizes the child given the parameters specified, and returns the computed layout. + */ +static ASLayout *crossChildLayout(const id child, + const ASStackLayoutSpecStyle style, + const CGFloat stackMin, + const CGFloat stackMax, + const CGFloat crossMin, + const CGFloat crossMax) +{ + const ASStackLayoutAlignItems alignItems = alignment(child.alignSelf, style.alignItems); + // stretched children will have a cross dimension of at least crossMin + const CGFloat childCrossMin = alignItems == ASStackLayoutAlignItemsStretch ? crossMin : 0; + const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, crossMax); + return [child measureWithSizeRange:childSizeRange]; +} + +/** + Stretches children to lay out along the cross axis according to the alignment stretch settings of the children + (child.alignSelf), and the stack layout's alignment settings (style.alignItems). This does not do the actual alignment + of the items once stretched though; ASStackPositionedLayout will do centering etc. + + Finds the maximum cross dimension among child layouts. If that dimension exceeds the minimum cross layout size then + we must stretch any children whose alignItems specify ASStackLayoutAlignItemsStretch. + + The diagram below shows 3 children in a horizontal stack. The second child is larger than the minCrossDimension, so + its height is used as the childCrossMax. Any children that are stretchable (which may be all children if + style.alignItems specifies stretch) like the first child must be stretched to match that maximum. All children must be + at least minCrossDimension in cross dimension size, which is shown by the sizing of the third child. + + Stack Dimension + +---------------------> + + +-+-------------+-+-------------+--+---------------+ + + + + | | child. | | | | | | | | + | | alignSelf | | | | | | | | + Cross | | = stretch | | | +-------+-------+ | | | + Dimension | +-----+-------+ | | | | | | | | + | | | | | | | | | | + | | | | | v | | | | + v +-+- - - - - - -+-+ - - - - - - +--+- - - - - - - -+ | | + minCrossDimension + | | | | | + | v | | | | | + +- - - - - - -+ +-------------+ | + childCrossMax + | + +--------------------------------------------------+ + crossMax + + @param layouts pre-computed child layouts; modified in-place as needed + @param style the layout style of the overall stack layout + */ +static void stretchChildrenAlongCrossDimension(std::vector &layouts, + const ASStackLayoutSpecStyle &style) +{ + // Find the maximum cross dimension size among child layouts + const auto it = std::max_element(layouts.begin(), layouts.end(), + [&](const ASStackUnpositionedItem &a, const ASStackUnpositionedItem &b) { + return compareCrossDimension(style.direction, a.layout.size, b.layout.size); + }); + + const CGFloat childCrossMax = it == layouts.end() ? 0 : crossDimension(style.direction, it->layout.size); + for (auto &l : layouts) { + const ASStackLayoutAlignItems alignItems = alignment(l.child.alignSelf, style.alignItems); + + const CGFloat cross = crossDimension(style.direction, l.layout.size); + const CGFloat stack = stackDimension(style.direction, l.layout.size); + + // restretch all stretchable children along the cross axis using the new min. set their max size to childCrossMax, + // not crossMax, so that if any of them would choose a larger size just because the min size increased (weird!) + // they are forced to choose the same width as all the other children. + if (alignItems == ASStackLayoutAlignItemsStretch && fabs(cross - childCrossMax) > 0.01) { + l.layout = crossChildLayout(l.child, style, stack, stack, childCrossMax, childCrossMax); + } + } +} + +/** + Computes the consumed stack dimension length for the given vector of children and stacking style. + + stackDimensionSum + <-----------------------> + +-----+ +-------+ +---+ + | | | | | | + | | | | | | + +-----+ | | +---+ + +-------+ + + @param children unpositioned layouts for the children of the stack spec + @param style the layout style of the overall stack layout + */ +static CGFloat computeStackDimensionSum(const std::vector &children, + const ASStackLayoutSpecStyle &style) +{ + // Sum up the childrens' spacing + const CGFloat childSpacingSum = std::accumulate(children.begin(), children.end(), + // Start from default spacing between each child: + children.empty() ? 0 : style.spacing * (children.size() - 1), + [&](CGFloat x, const ASStackUnpositionedItem &l) { + return x + l.child.spacingBefore + l.child.spacingAfter; + }); + + // Sum up the childrens' dimensions (including spacing) in the stack direction. + const CGFloat childStackDimensionSum = std::accumulate(children.begin(), children.end(), childSpacingSum, + [&](CGFloat x, const ASStackUnpositionedItem &l) { + return x + stackDimension(style.direction, l.layout.size); + }); + return childStackDimensionSum; +} + +/** + Computes the violation by comparing a stack dimension sum with the overall allowable size range for the stack. + + Violation is the distance you would have to add to the unbounded stack-direction length of the stack spec's + children in order to bring the stack within its allowed sizeRange. The diagram below shows 3 horizontal stacks with + the different types of violation. + + sizeRange + |------------| + +------+ +-------+ +-------+ +---------+ + | | | | | | | | | | + | | | | | | | | (zero violation) + | | | | | | | | | | + +------+ +-------+ +-------+ +---------+ + | | + +------+ +-------+ +-------+ + | | | | | | | | + | | | | | |<--> (positive violation) + | | | | | | | | + +------+ +-------+ +-------+ + | |<------> (negative violation) + +------+ +-------+ +-------+ +---------+ +-----------+ + | | | | | | | | | | | | + | | | | | | | | | | + | | | | | | | | | | | | + +------+ +-------+ +-------+ +---------+ +-----------+ + + @param stackDimensionSum the consumed length of the children in the stack along the stack dimension + @param style layout style to be applied to all children + @param sizeRange the range of allowable sizes for the stack layout spec + */ +static CGFloat computeViolation(const CGFloat stackDimensionSum, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange) +{ + const CGFloat minStackDimension = stackDimension(style.direction, sizeRange.min); + const CGFloat maxStackDimension = stackDimension(style.direction, sizeRange.max); + if (stackDimensionSum < minStackDimension) { + return minStackDimension - stackDimensionSum; + } else if (stackDimensionSum > maxStackDimension) { + return maxStackDimension - stackDimensionSum; + } + return 0; +} + +/** The threshold that determines if a violation has actually occurred. */ +static const CGFloat kViolationEpsilon = 0.01; + +/** + Returns a lambda that determines if the given unpositioned item's child is flexible in the direction of the violation. + + @param violation the amount that the stack layout violates its size range. See header for sign interpretation. + */ +static std::function isFlexibleInViolationDirection(const CGFloat violation) +{ + if (fabs(violation) < kViolationEpsilon) { + return [](const ASStackUnpositionedItem &l) { return NO; }; + } else if (violation > 0) { + return [](const ASStackUnpositionedItem &l) { return l.child.flexGrow; }; + } else { + return [](const ASStackUnpositionedItem &l) { return l.child.flexShrink; }; + } +} + +ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id child) +{ + return child.flexGrow && child.flexShrink; +} + +/** + 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> &children, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange) +{ + const NSUInteger flexibleChildren = std::count_if(children.begin(), children.end(), isFlexibleInBothDirections); + return ((flexibleChildren == 1) + && (stackDimension(style.direction, sizeRange.min) == + stackDimension(style.direction, sizeRange.max))); +} + +/** + The flexible children may have been left not laid out in the initial layout pass, so we may have to go through and size + these children at zero size so that the children layouts are at least present. + */ +static void layoutFlexibleChildrenAtZeroSize(std::vector &items, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange) +{ + for (ASStackUnpositionedItem &item : items) { + if (isFlexibleInBothDirections(item.child)) { + item.layout = crossChildLayout(item.child, + style, + 0, + 0, + crossDimension(style.direction, sizeRange.min), + crossDimension(style.direction, sizeRange.max)); + } + } +} + +/** + Flexes children in the stack axis to resolve a min or max stack size violation. First, determines which children are + flexible (see computeViolation and isFlexibleInViolationDirection). Then computes how much to flex each flexible child + and performs re-layout. Note that there may still be a non-zero violation even after flexing. + + The actual CSS flexbox spec describes an iterative looping algorithm here, which may be adopted in t5837937: + http://www.w3.org/TR/css3-flexbox/#resolve-flexible-lengths + + @param items Reference to unpositioned items from the original, unconstrained layout pass; modified in-place + @param style layout style to be applied to all children + @param sizeRange the range of allowable sizes for the stack layout spec + */ +static void flexChildrenAlongStackDimension(std::vector &items, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange, + const BOOL useOptimizedFlexing) +{ + const CGFloat stackDimensionSum = computeStackDimensionSum(items, style); + const CGFloat violation = computeViolation(stackDimensionSum, style, sizeRange); + + // We count the number of children which are flexible in the direction of the violation + std::function isFlex = isFlexibleInViolationDirection(violation); + const NSUInteger flexibleChildren = std::count_if(items.begin(), items.end(), isFlex); + if (flexibleChildren == 0) { + // If optimized flexing was used then we have to clean up the unsized children, and lay them out at zero size + if (useOptimizedFlexing) { + layoutFlexibleChildrenAtZeroSize(items, style, sizeRange); + } + return; + } + + // Each flexible child along the direction of the violation is expanded or contracted equally + const CGFloat violationPerFlexChild = floorf(violation / flexibleChildren); + // If the floor operation above left a remainder we may have a remainder after deducting the adjustments from all the + // contributions of the flexible children. + const CGFloat violationRemainder = violation - (violationPerFlexChild * flexibleChildren); + + BOOL isFirstFlex = YES; + for (ASStackUnpositionedItem &item : items) { + if (isFlex(item)) { + const CGFloat originalStackSize = stackDimension(style.direction, item.layout.size); + // The first flexible child is given the additional violation remainder + const CGFloat flexedStackSize = originalStackSize + violationPerFlexChild + (isFirstFlex ? violationRemainder : 0); + item.layout = crossChildLayout(item.child, + style, + MAX(flexedStackSize, 0), + MAX(flexedStackSize, 0), + crossDimension(style.direction, sizeRange.min), + crossDimension(style.direction, sizeRange.max)); + isFirstFlex = NO; + } + } +} + +/** + Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and + stretched. + */ +static std::vector layoutChildrenAlongUnconstrainedStackDimension(const std::vector> &children, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange, + const CGSize size, + const BOOL useOptimizedFlexing) +{ + const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); + const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); + + return AS::map(children, [&](id child) -> ASStackUnpositionedItem { + const BOOL isUnconstrainedFlexBasis = ASRelativeDimensionEqualToRelativeDimension(ASRelativeDimensionUnconstrained, child.flexBasis); + const CGFloat exactStackDimension = ASRelativeDimensionResolve(child.flexBasis, stackDimension(style.direction, size)); + + if (useOptimizedFlexing && isFlexibleInBothDirections(child)) { + return { child, [ASLayout newWithLayoutableObject:child size:{0, 0}] }; + } else { + return { + child, + crossChildLayout(child, + style, + isUnconstrainedFlexBasis ? 0 : exactStackDimension, + isUnconstrainedFlexBasis ? INFINITY : exactStackDimension, + minCrossDimension, + maxCrossDimension) + }; + } + }); +} + +ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector> &children, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange) +{ + const CGSize size = { + sizeRange.max.width, + sizeRange.max.height + }; + + // We may be able to avoid some redundant layout passes + const BOOL optimizedFlexing = useOptimizedFlexing(children, style, sizeRange); + + // We do a first pass of all the children, generating an unpositioned layout for each with an unbounded range along + // the stack dimension. This allows us to compute the "intrinsic" size of each child and find the available violation + // which determines whether we must grow or shrink the flexible children. + std::vector items = layoutChildrenAlongUnconstrainedStackDimension(children, + style, + sizeRange, + size, + optimizedFlexing); + + flexChildrenAlongStackDimension(items, style, sizeRange, optimizedFlexing); + stretchChildrenAlongCrossDimension(items, style); + + const CGFloat stackDimensionSum = computeStackDimensionSum(items, style); + return {items, stackDimensionSum, computeViolation(stackDimensionSum, style, sizeRange)}; +} diff --git a/AsyncDisplayKit/Private/_ASPendingState.m b/AsyncDisplayKit/Private/_ASPendingState.m index dd7d33c3a2..b92f97d802 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.m +++ b/AsyncDisplayKit/Private/_ASPendingState.m @@ -48,6 +48,7 @@ BOOL accessibilityElementsHidden; BOOL accessibilityViewIsModal; BOOL shouldGroupAccessibilityChildren; + NSString *accessibilityIdentifier; struct { // Properties @@ -97,6 +98,7 @@ int setAccessibilityElementsHidden:1; int setAccessibilityViewIsModal:1; int setShouldGroupAccessibilityChildren:1; + int setAccessibilityIdentifier:1; } _flags; } @@ -187,6 +189,7 @@ accessibilityElementsHidden = NO; accessibilityViewIsModal = NO; shouldGroupAccessibilityChildren = NO; + accessibilityIdentifier = nil; edgeAntialiasingMask = (kCALayerLeftEdge | kCALayerRightEdge | kCALayerTopEdge | kCALayerBottomEdge); return self; @@ -542,6 +545,19 @@ _flags.setShouldGroupAccessibilityChildren = YES; } +- (NSString *)accessibilityIdentifier +{ + return accessibilityIdentifier; +} + +- (void)setAccessibilityIdentifier:(NSString *)newAccessibilityIdentifier +{ + _flags.setAccessibilityIdentifier = YES; + if (accessibilityIdentifier != newAccessibilityIdentifier) { + accessibilityIdentifier = [newAccessibilityIdentifier copy]; + } +} + - (void)applyToLayer:(CALayer *)layer { if (_flags.setAnchorPoint) @@ -775,6 +791,9 @@ if (_flags.setShouldGroupAccessibilityChildren) view.shouldGroupAccessibilityChildren = shouldGroupAccessibilityChildren; + + if (_flags.setAccessibilityIdentifier) + view.accessibilityIdentifier = accessibilityIdentifier; } @end diff --git a/AsyncDisplayKitTests/ASBasicImageDownloaderContextTests.m b/AsyncDisplayKitTests/ASBasicImageDownloaderContextTests.m index d1474f5ad6..47ce631ef6 100644 --- a/AsyncDisplayKitTests/ASBasicImageDownloaderContextTests.m +++ b/AsyncDisplayKitTests/ASBasicImageDownloaderContextTests.m @@ -42,6 +42,7 @@ XCTAssert([context isCancelled], @"Context should be cancelled"); } +/* This test is currently unreliable. See https://github.com/facebook/AsyncDisplayKit/issues/459 - (void)testAsyncContextInvalidation { NSURL *url = [self randomURL]; @@ -56,6 +57,7 @@ [context cancel]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; } +*/ - (void)testContextSessionCanceled { diff --git a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m new file mode 100644 index 0000000000..b174722341 --- /dev/null +++ b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m @@ -0,0 +1,53 @@ +// +// ASBasicImageDownloaderTests.m +// AsyncDisplayKit +// +// Created by Victor Mayorov on 10/06/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import + +#import + +@interface ASBasicImageDownloaderTests : XCTestCase + +@end + +@implementation ASBasicImageDownloaderTests + +- (void)testAsynchronouslyDownloadTheSameURLTwice { + ASBasicImageDownloader *downloader = [ASBasicImageDownloader new]; + + NSURL *URL = [NSURL URLWithString:@"http://wrongPath/wrongResource.png"]; + + dispatch_group_t group = dispatch_group_create(); + + __block BOOL firstDone = NO; + + dispatch_group_enter(group); + [downloader downloadImageWithURL:URL + callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) + downloadProgressBlock:nil + completion:^(CGImageRef image, NSError *error) { + firstDone = YES; + dispatch_group_leave(group); + }]; + + __block BOOL secondDone = NO; + + dispatch_group_enter(group); + [downloader downloadImageWithURL:URL + callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) + downloadProgressBlock:nil + completion:^(CGImageRef image, NSError *error) { + secondDone = YES; + dispatch_group_leave(group); + }]; + + XCTAssert(0 == dispatch_group_wait(group, dispatch_time(0, 10 * 1000000000)), @"URL loading takes too long"); + + XCTAssert(firstDone && secondDone, @"Not all handlers has been called"); +} + +@end diff --git a/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm new file mode 100644 index 0000000000..388e7d0e7e --- /dev/null +++ b/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm @@ -0,0 +1,111 @@ +/* + * 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 "ASLayoutSpecSnapshotTestsHelper.h" + +#import "ASBackgroundLayoutSpec.h" +#import "ASCenterLayoutSpec.h" +#import "ASStackLayoutSpec.h" + +static const ASSizeRange kSize = {{100, 120}, {320, 160}}; + +@interface ASCenterLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase +@end + +@implementation ASCenterLayoutSpecSnapshotTests + +- (void)setUp +{ + [super setUp]; + self.recordMode = NO; +} + +- (void)testWithOptions +{ + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:{}]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:{}]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringX sizingOptions:{}]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringY sizingOptions:{}]; +} + +- (void)testWithSizingOptions +{ + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionDefault]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionMinimumX]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionMinimumY]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionMinimumXY]; +} + +- (void)testWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)options + sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions +{ + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor]); + foregroundNode.staticSize = {70, 100}; + + ASLayoutSpec *layoutSpec = + [ASBackgroundLayoutSpec + newWithChild: + [ASCenterLayoutSpec + newWithCenteringOptions:options + sizingOptions:sizingOptions + child:foregroundNode] + background:backgroundNode]; + + [self testLayoutSpec:layoutSpec + sizeRange:kSize + subnodes:@[backgroundNode, foregroundNode] + identifier:suffixForCenteringOptions(options, sizingOptions)]; +} + +static NSString *suffixForCenteringOptions(ASCenterLayoutSpecCenteringOptions centeringOptions, + ASCenterLayoutSpecSizingOptions sizingOptinos) +{ + NSMutableString *suffix = [NSMutableString string]; + + if ((centeringOptions & ASCenterLayoutSpecCenteringX) != 0) { + [suffix appendString:@"CenteringX"]; + } + + if ((centeringOptions & ASCenterLayoutSpecCenteringY) != 0) { + [suffix appendString:@"CenteringY"]; + } + + if ((sizingOptinos & ASCenterLayoutSpecSizingOptionMinimumX) != 0) { + [suffix appendString:@"SizingMinimumX"]; + } + + if ((sizingOptinos & ASCenterLayoutSpecSizingOptionMinimumY) != 0) { + [suffix appendString:@"SizingMinimumY"]; + } + + return suffix; +} + +- (void)testMinimumSizeRangeIsGivenToChildWhenNotCentering +{ + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + foregroundNode.staticSize = {10, 10}; + foregroundNode.flexGrow = YES; + + ASCenterLayoutSpec *layoutSpec = + [ASCenterLayoutSpec + newWithCenteringOptions:ASCenterLayoutSpecCenteringNone + sizingOptions:{} + child: + [ASBackgroundLayoutSpec + newWithChild:[ASStackLayoutSpec newWithStyle:{} children:@[foregroundNode]] + background:backgroundNode]]; + + [self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier:nil]; +} + +@end diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.m b/AsyncDisplayKitTests/ASCollectionViewTests.m new file mode 100644 index 0000000000..3b4d661514 --- /dev/null +++ b/AsyncDisplayKitTests/ASCollectionViewTests.m @@ -0,0 +1,93 @@ +// +// ASCollectionViewTests.m +// AsyncDisplayKit +// +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import +#import + +@interface ASCollectionViewTestDelegate : NSObject + +@property (nonatomic, assign) NSInteger numberOfSections; +@property (nonatomic, assign) NSInteger numberOfItemsInSection; + +@end + +@implementation ASCollectionViewTestDelegate + +- (id)initWithNumberOfSections:(NSInteger)numberOfSections numberOfItemsInSection:(NSInteger)numberOfItemsInSection { + if (self = [super init]) { + _numberOfSections = numberOfSections; + _numberOfItemsInSection = numberOfItemsInSection; + } + + return self; +} + +- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath { + ASTextCellNode *textCellNode = [ASTextCellNode new]; + textCellNode.text = indexPath.description; + + return textCellNode; +} + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return self.numberOfSections; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.numberOfItemsInSection; +} + +@end + +@interface ASCollectionViewTestController: UIViewController + +@property (nonatomic, strong) ASCollectionViewTestDelegate *asyncDelegate; +@property (nonatomic, strong) ASCollectionView *collectionView; + +@end + +@implementation ASCollectionViewTestController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.asyncDelegate = [[ASCollectionViewTestDelegate alloc] initWithNumberOfSections:10 numberOfItemsInSection:10]; + + self.collectionView = [[ASCollectionView alloc] initWithFrame:self.view.bounds + collectionViewLayout:[UICollectionViewFlowLayout new]]; + self.collectionView.asyncDataSource = self.asyncDelegate; + self.collectionView.asyncDelegate = self.asyncDelegate; + + [self.view addSubview:self.collectionView]; +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + + self.collectionView.frame = self.view.bounds; +} + +@end + +@interface ASCollectionViewTests : XCTestCase + +@end + +@implementation ASCollectionViewTests + +- (void)DISABLED_testCollectionViewController { + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + + UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + [containerView addSubview:testController.view]; + + [testController.collectionView reloadData]; + + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; +} + +@end diff --git a/AsyncDisplayKitTests/ASDimensionTests.mm b/AsyncDisplayKitTests/ASDimensionTests.mm new file mode 100644 index 0000000000..3c32fdea59 --- /dev/null +++ b/AsyncDisplayKitTests/ASDimensionTests.mm @@ -0,0 +1,86 @@ +/* + * 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 + +#import "ASDimension.h" + + +@interface ASDimensionTests : XCTestCase +@end + +@implementation ASDimensionTests + +- (void)testIntersectingOverlappingSizeRangesReturnsTheirIntersection +{ + // range: |---------| + // other: |----------| + // result: |----| + + ASSizeRange range = {{0,0}, {10,10}}; + ASSizeRange other = {{7,7}, {15,15}}; + ASSizeRange result = ASSizeRangeIntersect(range, other); + ASSizeRange expected = {{7,7}, {10,10}}; + XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result)); +} + +- (void)testIntersectingSizeRangeWithRangeThatContainsItReturnsSameRange +{ + // range: |-----| + // other: |---------| + // result: |-----| + + ASSizeRange range = {{2,2}, {8,8}}; + ASSizeRange other = {{0,0}, {10,10}}; + ASSizeRange result = ASSizeRangeIntersect(range, other); + ASSizeRange expected = {{2,2}, {8,8}}; + XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result)); +} + +- (void)testIntersectingSizeRangeWithRangeContainedWithinItReturnsContainedRange +{ + // range: |---------| + // other: |-----| + // result: |-----| + + ASSizeRange range = {{0,0}, {10,10}}; + ASSizeRange other = {{2,2}, {8,8}}; + ASSizeRange result = ASSizeRangeIntersect(range, other); + ASSizeRange expected = {{2,2}, {8,8}}; + XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result)); +} + +- (void)testIntersectingSizeRangeWithNonOverlappingRangeToRightReturnsSinglePointNearestOtherRange +{ + // range: |-----| + // other: |---| + // result: * + + ASSizeRange range = {{0,0}, {5,5}}; + ASSizeRange other = {{10,10}, {15,15}}; + ASSizeRange result = ASSizeRangeIntersect(range, other); + ASSizeRange expected = {{5,5}, {5,5}}; + XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result)); +} + +- (void)testIntersectingSizeRangeWithNonOverlappingRangeToLeftReturnsSinglePointNearestOtherRange +{ + // range: |---| + // other: |-----| + // result: * + + ASSizeRange range = {{10,10}, {15,15}}; + ASSizeRange other = {{0,0}, {5,5}}; + ASSizeRange result = ASSizeRangeIntersect(range, other); + ASSizeRange expected = {{10,10}, {10,10}}; + XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result)); +} + +@end diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index b5b0d84644..4ce0c49bcf 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -71,6 +71,9 @@ for (ASDisplayNode *n in @[ nodes ]) {\ @property (atomic, copy) CGSize(^calculateSizeBlock)(ASTestDisplayNode *node, CGSize size); @end +@interface ASTestResponderNode : ASTestDisplayNode +@end + @implementation ASTestDisplayNode - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize @@ -91,9 +94,57 @@ for (ASDisplayNode *n in @[ nodes ]) {\ @interface UIDisplayNodeTestView : UIView @end +@interface UIResponderNodeTestView : _ASDisplayView +@property(nonatomic) BOOL isFirstResponder; +@end + @implementation UIDisplayNodeTestView @end +@interface ASTestWindow : UIWindow +@end + +@implementation ASTestWindow + +- (id)firstResponder { + return self.subviews.firstObject; +} + +@end + +@implementation ASTestResponderNode + ++ (Class)viewClass { + return [UIResponderNodeTestView class]; +} + +- (BOOL)canBecomeFirstResponder { + return YES; +} + +@end + +@implementation UIResponderNodeTestView + +- (BOOL)becomeFirstResponder { + self.isFirstResponder = YES; + return YES; +} + +- (BOOL)canResignFirstResponder { + return YES; +} + +- (BOOL)resignFirstResponder { + if (self.isFirstResponder) { + self.isFirstResponder = NO; + return YES; + } + return NO; +} + +@end + @interface ASDisplayNodeTests : XCTestCase @end @@ -102,6 +153,25 @@ for (ASDisplayNode *n in @[ nodes ]) {\ dispatch_queue_t queue; } +- (void)testOverriddenFirstResponderBehavior { + ASTestDisplayNode *node = [[ASTestResponderNode alloc] init]; + XCTAssertTrue([node canBecomeFirstResponder]); + XCTAssertTrue([node becomeFirstResponder]); +} + +- (void)testDefaultFirstResponderBehavior { + ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init]; + XCTAssertFalse([node canBecomeFirstResponder]); + XCTAssertFalse([node becomeFirstResponder]); +} + +- (void)testLayerBackedFirstResponderBehavior { + ASTestDisplayNode *node = [[ASTestResponderNode alloc] init]; + node.layerBacked = YES; + XCTAssertTrue([node canBecomeFirstResponder]); + XCTAssertFalse([node becomeFirstResponder]); +} + - (void)setUp { [super setUp]; @@ -1680,5 +1750,16 @@ static bool stringContainsPointer(NSString *description, const void *p) { [self checkNameInDescriptionIsLayerBacked:NO]; } +- (void)testBounds +{ + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.bounds = CGRectMake(1, 2, 3, 4); + node.frame = CGRectMake(5, 6, 7, 8); + + XCTAssert(node.bounds.origin.x == 1, @"Wrong ASDisplayNode.bounds.origin.x"); + XCTAssert(node.bounds.origin.y == 2, @"Wrong ASDisplayNode.bounds.origin.y"); + XCTAssert(node.bounds.size.width == 7, @"Wrong ASDisplayNode.bounds.size.width"); + XCTAssert(node.bounds.size.height == 8, @"Wrong ASDisplayNode.bounds.size.height"); +} @end diff --git a/AsyncDisplayKitTests/ASInsetLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASInsetLayoutSpecSnapshotTests.mm new file mode 100644 index 0000000000..9ff4bf219e --- /dev/null +++ b/AsyncDisplayKitTests/ASInsetLayoutSpecSnapshotTests.mm @@ -0,0 +1,118 @@ +/* + * 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 "ASLayoutSpecSnapshotTestsHelper.h" + +#import "ASBackgroundLayoutSpec.h" +#import "ASInsetLayoutSpec.h" + +typedef NS_OPTIONS(NSUInteger, ASInsetLayoutSpecTestEdge) { + ASInsetLayoutSpecTestEdgeTop = 1 << 0, + ASInsetLayoutSpecTestEdgeLeft = 1 << 1, + ASInsetLayoutSpecTestEdgeBottom = 1 << 2, + ASInsetLayoutSpecTestEdgeRight = 1 << 3, +}; + +static CGFloat insetForEdge(NSUInteger combination, ASInsetLayoutSpecTestEdge edge, CGFloat insetValue) +{ + return combination & edge ? INFINITY : insetValue; +} + +static UIEdgeInsets insetsForCombination(NSUInteger combination, CGFloat insetValue) +{ + return { + .top = insetForEdge(combination, ASInsetLayoutSpecTestEdgeTop, insetValue), + .left = insetForEdge(combination, ASInsetLayoutSpecTestEdgeLeft, insetValue), + .bottom = insetForEdge(combination, ASInsetLayoutSpecTestEdgeBottom, insetValue), + .right = insetForEdge(combination, ASInsetLayoutSpecTestEdgeRight, insetValue), + }; +} + +static NSString *nameForInsets(UIEdgeInsets insets) +{ + return [NSString stringWithFormat:@"%.f-%.f-%.f-%.f", insets.top, insets.left, insets.bottom, insets.right]; +} + +@interface ASInsetLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase +@end + +@implementation ASInsetLayoutSpecSnapshotTests + +- (void)setUp +{ + [super setUp]; + self.recordMode = NO; +} + +- (void)testInsetsWithVariableSize +{ + for (NSUInteger combination = 0; combination < 16; combination++) { + UIEdgeInsets insets = insetsForCombination(combination, 10); + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor grayColor]); + ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor]); + foregroundNode.staticSize = {10, 10}; + + ASLayoutSpec *layoutSpec = + [ASBackgroundLayoutSpec + newWithChild:[ASInsetLayoutSpec newWithInsets:insets child:foregroundNode] + background:backgroundNode]; + + static ASSizeRange kVariableSize = {{0, 0}, {300, 300}}; + [self testLayoutSpec:layoutSpec + sizeRange:kVariableSize + subnodes:@[backgroundNode, foregroundNode] + identifier:nameForInsets(insets)]; + } +} + +- (void)testInsetsWithFixedSize +{ + for (NSUInteger combination = 0; combination < 16; combination++) { + UIEdgeInsets insets = insetsForCombination(combination, 10); + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor grayColor]); + ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor]); + foregroundNode.staticSize = {10, 10}; + + ASLayoutSpec *layoutSpec = + [ASBackgroundLayoutSpec + newWithChild:[ASInsetLayoutSpec newWithInsets:insets child:foregroundNode] + background:backgroundNode]; + + static ASSizeRange kFixedSize = {{300, 300}, {300, 300}}; + [self testLayoutSpec:layoutSpec + sizeRange:kFixedSize + subnodes:@[backgroundNode, foregroundNode] + identifier:nameForInsets(insets)]; + } +} + +/** Regression test, there was a bug mixing insets with infinite and zero sizes */ +- (void)testInsetsWithInfinityAndZeroInsetValue +{ + for (NSUInteger combination = 0; combination < 16; combination++) { + UIEdgeInsets insets = insetsForCombination(combination, 0); + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor grayColor]); + ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor]); + foregroundNode.staticSize = {10, 10}; + + ASLayoutSpec *layoutSpec = + [ASBackgroundLayoutSpec + newWithChild:[ASInsetLayoutSpec newWithInsets:insets child:foregroundNode] + background:backgroundNode]; + + static ASSizeRange kFixedSize = {{300, 300}, {300, 300}}; + [self testLayoutSpec:layoutSpec + sizeRange:kFixedSize + subnodes:@[backgroundNode, foregroundNode] + identifier:nameForInsets(insets)]; + } +} + +@end diff --git a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.h b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.h new file mode 100644 index 0000000000..362381ffee --- /dev/null +++ b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import "ASSnapshotTestCase.h" +#import "ASDisplayNode+Subclasses.h" + +@class ASLayoutSpec; + +@interface ASLayoutSpecSnapshotTestCase: ASSnapshotTestCase +/** + Test the layout spec or records a snapshot if recordMode is YES. + @param layoutSpec The layout spec under test or to snapshot + @param sizeRange The size range used to calculate layout of the given layout spec. + @param subnodes An array of ASDisplayNodes used within the layout spec. + @param identifier An optional identifier, used to identify this snapshot test. + + @discussion In order to make the layout spec visible, it is embeded to a ASDisplayNode host. + Any subnodes used within the layout spec must be provided. + They will be added to the host in the same order as the array. + */ +- (void)testLayoutSpec:(ASLayoutSpec *)layoutSpec + sizeRange:(ASSizeRange)sizeRange + subnodes:(NSArray *)subnodes + identifier:(NSString *)identifier; +@end + +@interface ASStaticSizeDisplayNode : ASDisplayNode + +@property (nonatomic) CGSize staticSize; + +@end + +static inline ASStaticSizeDisplayNode *ASDisplayNodeWithBackgroundColor(UIColor *backgroundColor) +{ + ASStaticSizeDisplayNode *node = [[ASStaticSizeDisplayNode alloc] init]; + node.layerBacked = YES; + node.backgroundColor = backgroundColor; + node.staticSize = CGSizeZero; + return node; +} diff --git a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m new file mode 100644 index 0000000000..c15b28f6d4 --- /dev/null +++ b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import "ASLayoutSpecSnapshotTestsHelper.h" + +#import "ASDisplayNode.h" +#import "ASLayoutSpec.h" + +@interface ASTestNode : ASDisplayNode +- (void)setLayoutSpecUnderTest:(ASLayoutSpec *)layoutSpecUnderTest sizeRange:(ASSizeRange)sizeRange; +@end + +@implementation ASLayoutSpecSnapshotTestCase + +- (void)testLayoutSpec:(ASLayoutSpec *)layoutSpec + sizeRange:(ASSizeRange)sizeRange + subnodes:(NSArray *)subnodes + identifier:(NSString *)identifier +{ + ASTestNode *node = [[ASTestNode alloc] init]; + + for (ASDisplayNode *subnode in subnodes) { + [node addSubnode:subnode]; + } + + [node setLayoutSpecUnderTest:layoutSpec sizeRange:sizeRange]; + + ASSnapshotVerifyNode(node, identifier); +} + +@end + +@implementation ASTestNode +{ + ASLayout *_layoutUnderTest; +} + +- (instancetype)init +{ + if (self = [super init]) { + self.layerBacked = YES; + } + return self; +} + +- (void)setLayoutSpecUnderTest:(ASLayoutSpec *)layoutSpecUnderTest sizeRange:(ASSizeRange)sizeRange +{ + ASLayout *layout = [layoutSpecUnderTest measureWithSizeRange:sizeRange]; + layout.position = CGPointZero; + layout = [ASLayout newWithLayoutableObject:self size:layout.size sublayouts:@[layout]]; + _layoutUnderTest = [layout flattenedLayoutUsingPredicateBlock:^BOOL(ASLayout *evaluatedLayout) { + return [self.subnodes containsObject:evaluatedLayout.layoutableObject]; + }]; + self.frame = CGRectMake(0, 0, _layoutUnderTest.size.width, _layoutUnderTest.size.height); + [self measure:_layoutUnderTest.size]; +} + +- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize +{ + return _layoutUnderTest; +} + +@end + +@implementation ASStaticSizeDisplayNode + +- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize +{ + return _staticSize; +} + +@end diff --git a/AsyncDisplayKitTests/ASMutableAttributedStringBuilderTests.m b/AsyncDisplayKitTests/ASMutableAttributedStringBuilderTests.m index 0d6cfbe269..9a43f5f434 100644 --- a/AsyncDisplayKitTests/ASMutableAttributedStringBuilderTests.m +++ b/AsyncDisplayKitTests/ASMutableAttributedStringBuilderTests.m @@ -29,7 +29,8 @@ - (NSRange)_randomizedRangeForStringBuilder:(ASMutableAttributedStringBuilder *)builder { NSUInteger loc = arc4random() % (builder.length - 1); - NSUInteger len = MAX(arc4random() % (builder.length - loc), 1); + NSUInteger len = arc4random() % (builder.length - loc); + len = ((len > 0) ? len : 1); return NSMakeRange(loc, len); } diff --git a/AsyncDisplayKitTests/ASOverlayLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASOverlayLayoutSpecSnapshotTests.mm new file mode 100644 index 0000000000..066a6c47b5 --- /dev/null +++ b/AsyncDisplayKitTests/ASOverlayLayoutSpecSnapshotTests.mm @@ -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 "ASLayoutSpecSnapshotTestsHelper.h" + +#import "ASOverlayLayoutSpec.h" +#import "ASCenterLayoutSpec.h" + +static const ASSizeRange kSize = {{320, 320}, {320, 320}}; + +@interface ASOverlayLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase +@end + +@implementation ASOverlayLayoutSpecSnapshotTests + +- (void)setUp +{ + [super setUp]; + self.recordMode = NO; +} + +- (void)testOverlay +{ + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]); + ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor blackColor]); + foregroundNode.staticSize = {20, 20}; + + ASLayoutSpec *layoutSpec = + [ASOverlayLayoutSpec + newWithChild:backgroundNode + overlay: + [ASCenterLayoutSpec + newWithCenteringOptions:ASCenterLayoutSpecCenteringXY + sizingOptions:{} + child:foregroundNode]]; + + [self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier: nil]; +} + +@end diff --git a/AsyncDisplayKitTests/ASRatioLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASRatioLayoutSpecSnapshotTests.mm new file mode 100644 index 0000000000..ffe30e0f0d --- /dev/null +++ b/AsyncDisplayKitTests/ASRatioLayoutSpecSnapshotTests.mm @@ -0,0 +1,46 @@ +/* + * 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 "ASLayoutSpecSnapshotTestsHelper.h" + +#import "ASRatioLayoutSpec.h" + +static const ASSizeRange kFixedSize = {{0, 0}, {100, 100}}; + +@interface ASRatioLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase +@end + +@implementation ASRatioLayoutSpecSnapshotTests + +- (void)setUp +{ + [super setUp]; + self.recordMode = NO; +} + +- (void)testRatioLayoutSpecWithRatio:(CGFloat)ratio childSize:(CGSize)childSize identifier:(NSString *)identifier +{ + ASStaticSizeDisplayNode *subnode = ASDisplayNodeWithBackgroundColor([UIColor greenColor]); + subnode.staticSize = childSize; + + ASLayoutSpec *layoutSpec = [ASRatioLayoutSpec newWithRatio:ratio child:subnode]; + + [self testLayoutSpec:layoutSpec sizeRange:kFixedSize subnodes:@[subnode] identifier:identifier]; +} + +- (void)testRatioLayout +{ + [self testRatioLayoutSpecWithRatio:0.5 childSize:CGSizeMake(100, 100) identifier:@"HalfRatio"]; + [self testRatioLayoutSpecWithRatio:2.0 childSize:CGSizeMake(100, 100) identifier:@"DoubleRatio"]; + [self testRatioLayoutSpecWithRatio:7.0 childSize:CGSizeMake(100, 100) identifier:@"SevenTimesRatio"]; + [self testRatioLayoutSpecWithRatio:10.0 childSize:CGSizeMake(20, 200) identifier:@"TenTimesRatioWithItemTooBig"]; +} + +@end diff --git a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm new file mode 100644 index 0000000000..21a885444e --- /dev/null +++ b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm @@ -0,0 +1,532 @@ +/* + * 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 "ASLayoutSpecSnapshotTestsHelper.h" + +#import "ASStackLayoutSpec.h" +#import "ASBackgroundLayoutSpec.h" +#import "ASRatioLayoutSpec.h" +#import "ASInsetLayoutSpec.h" + +@interface ASStackLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase +@end + +@implementation ASStackLayoutSpecSnapshotTests + +- (void)setUp +{ + [super setUp]; + self.recordMode = NO; +} + +static NSArray *defaultSubnodes() +{ + return defaultSubnodesWithSameSize(CGSizeZero, NO); +} + +static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex) +{ + NSArray *subnodes = @[ + ASDisplayNodeWithBackgroundColor([UIColor redColor]), + ASDisplayNodeWithBackgroundColor([UIColor blueColor]), + ASDisplayNodeWithBackgroundColor([UIColor greenColor]) + ]; + for (ASStaticSizeDisplayNode *subnode in subnodes) { + subnode.staticSize = subnodeSize; + subnode.flexGrow = flex; + subnode.flexShrink = flex; + } + return subnodes; +} + +- (void)testStackLayoutSpecWithJustify:(ASStackLayoutJustifyContent)justify + flex:(BOOL)flex + sizeRange:(ASSizeRange)sizeRange + identifier:(NSString *)identifier +{ + ASStackLayoutSpecStyle style = { + .direction = ASStackLayoutDirectionHorizontal, + .justifyContent = justify + }; + + NSArray *subnodes = defaultSubnodesWithSameSize({50, 50}, flex); + + [self testStackLayoutSpecWithStyle:style sizeRange:sizeRange subnodes:subnodes identifier:identifier]; +} + +- (void)testStackLayoutSpecWithStyle:(ASStackLayoutSpecStyle)style + sizeRange:(ASSizeRange)sizeRange + subnodes:(NSArray *)subnodes + identifier:(NSString *)identifier +{ + [self testStackLayoutSpecWithStyle:style children:subnodes sizeRange:sizeRange subnodes:subnodes identifier:identifier]; +} + +- (void)testStackLayoutSpecWithStyle:(ASStackLayoutSpecStyle)style + children:(NSArray *)children + sizeRange:(ASSizeRange)sizeRange + subnodes:(NSArray *)subnodes + identifier:(NSString *)identifier +{ + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor whiteColor]); + + ASLayoutSpec *layoutSpec = + [ASBackgroundLayoutSpec + newWithChild:[ASStackLayoutSpec newWithStyle:style children:children] + background:backgroundNode]; + + NSMutableArray *newSubnodes = [NSMutableArray arrayWithObject:backgroundNode]; + [newSubnodes addObjectsFromArray:subnodes]; + + [self testLayoutSpec:layoutSpec sizeRange:sizeRange subnodes:newSubnodes identifier:identifier]; +} + +- (void)testUnderflowBehaviors +{ + // width 300px; height 0-300px + static ASSizeRange kSize = {{300, 0}, {300, 300}}; + [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:NO sizeRange:kSize identifier:@"justifyStart"]; + [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"]; + [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"]; + [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:YES sizeRange:kSize identifier:@"flex"]; +} + +- (void)testOverflowBehaviors +{ + // width 110px; height 0-300px + static ASSizeRange kSize = {{110, 0}, {110, 300}}; + [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:NO sizeRange:kSize identifier:@"justifyStart"]; + [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"]; + [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"]; + [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:YES sizeRange:kSize identifier:@"flex"]; +} + +- (void)testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists +{ + ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; + + NSArray *subnodes = defaultSubnodesWithSameSize({50, 50}, NO); + ((ASDisplayNode *)subnodes[1]).flexShrink = YES; + + // Width is 75px--that's less than the sum of the widths of the children, which is 100px. + static ASSizeRange kSize = {{75, 0}, {75, 150}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; +} + +- (void)testFlexWithUnequalIntrinsicSizes +{ + ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; + + NSArray *subnodes = defaultSubnodesWithSameSize({50, 50}, YES); + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {150, 150}; + + // width 300px; height 0-150px. + static ASSizeRange kUnderflowSize = {{300, 0}, {300, 150}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kUnderflowSize subnodes:subnodes identifier:@"underflow"]; + + // width 200px; height 0-150px. + static ASSizeRange kOverflowSize = {{200, 0}, {200, 150}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kOverflowSize subnodes:subnodes identifier:@"overflow"]; +} + +- (void)testCrossAxisSizeBehaviors +{ + ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical}; + + NSArray *subnodes = defaultSubnodes(); + ((ASStaticSizeDisplayNode *)subnodes[0]).staticSize = {50, 50}; + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {100, 50}; + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {150, 50}; + + // width 0-300px; height 300px + static ASSizeRange kVariableHeight = {{0, 300}, {300, 300}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kVariableHeight subnodes:subnodes identifier:@"variableHeight"]; + + // width 300px; height 300px + static ASSizeRange kFixedHeight = {{300, 300}, {300, 300}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kFixedHeight subnodes:subnodes identifier:@"fixedHeight"]; +} + +- (void)testStackSpacing +{ + ASStackLayoutSpecStyle style = { + .direction = ASStackLayoutDirectionVertical, + .spacing = 10 + }; + + NSArray *subnodes = defaultSubnodes(); + ((ASStaticSizeDisplayNode *)subnodes[0]).staticSize = {50, 50}; + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {100, 50}; + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {150, 50}; + + // width 0-300px; height 300px + static ASSizeRange kVariableHeight = {{0, 300}, {300, 300}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kVariableHeight subnodes:subnodes identifier:@"variableHeight"]; +} + +- (void)testStackSpacingWithChildrenHavingNilObjects +{ + // This should take a zero height since all children have a nil node. If it takes a height > 0, a blue background + // will show up, hence failing the test. + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]); + + ASLayoutSpec *layoutSpec = + [ASInsetLayoutSpec + newWithInsets:{10, 10, 10 ,10} + child: + [ASBackgroundLayoutSpec + newWithChild: + [ASStackLayoutSpec + newWithStyle:{ + .direction = ASStackLayoutDirectionVertical, + .spacing = 10, + .alignItems = ASStackLayoutAlignItemsStretch + } + children:@[]] + background:backgroundNode]]; + + // width 300px; height 0-300px + static ASSizeRange kVariableHeight = {{300, 0}, {300, 300}}; + [self testLayoutSpec:layoutSpec sizeRange:kVariableHeight subnodes:@[backgroundNode] identifier:@"variableHeight"]; +} + +- (void)testChildSpacing +{ + // width 0-INF; height 0-INF + static ASSizeRange kAnySize = {{0, 0}, {INFINITY, INFINITY}}; + ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical}; + + NSArray *subnodes = defaultSubnodes(); + ((ASStaticSizeDisplayNode *)subnodes[0]).staticSize = {50, 50}; + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {100, 70}; + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {150, 90}; + + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingBefore = 10; + ((ASStaticSizeDisplayNode *)subnodes[2]).spacingBefore = 20; + [self testStackLayoutSpecWithStyle:style sizeRange:kAnySize subnodes:subnodes identifier:@"spacingBefore"]; + // Reset above spacing values + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingBefore = 0; + ((ASStaticSizeDisplayNode *)subnodes[2]).spacingBefore = 0; + + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingAfter = 10; + ((ASStaticSizeDisplayNode *)subnodes[2]).spacingAfter = 20; + [self testStackLayoutSpecWithStyle:style sizeRange:kAnySize subnodes:subnodes identifier:@"spacingAfter"]; + // Reset above spacing values + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingAfter = 0; + ((ASStaticSizeDisplayNode *)subnodes[2]).spacingAfter = 0; + + style.spacing = 10; + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingBefore = -10; + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingAfter = -10; + [self testStackLayoutSpecWithStyle:style sizeRange:kAnySize subnodes:subnodes identifier:@"spacingBalancedOut"]; +} + +- (void)testJustifiedCenterWithChildSpacing +{ + ASStackLayoutSpecStyle style = { + .direction = ASStackLayoutDirectionVertical, + .justifyContent = ASStackLayoutJustifyContentCenter + }; + + NSArray *subnodes = defaultSubnodes(); + ((ASStaticSizeDisplayNode *)subnodes[0]).staticSize = {50, 50}; + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {100, 70}; + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {150, 90}; + + ((ASStaticSizeDisplayNode *)subnodes[0]).spacingBefore = 0; + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingBefore = 20; + ((ASStaticSizeDisplayNode *)subnodes[2]).spacingBefore = 30; + + // width 0-300px; height 300px + static ASSizeRange kVariableHeight = {{0, 300}, {300, 300}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kVariableHeight subnodes:subnodes identifier:@"variableHeight"]; +} + +- (void)testChildThatChangesCrossSizeWhenMainSizeIsFlexed +{ + ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; + + ASStaticSizeDisplayNode * subnode1 = ASDisplayNodeWithBackgroundColor([UIColor blueColor]); + ASStaticSizeDisplayNode * subnode2 = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + subnode2.staticSize = {50, 50}; + + ASRatioLayoutSpec *child1 = [ASRatioLayoutSpec newWithRatio:1.5 child:subnode1]; + child1.flexBasis = ASRelativeDimensionMakeWithPercent(1); + child1.flexGrow = YES; + child1.flexShrink = YES; + + static ASSizeRange kFixedWidth = {{150, 0}, {150, INFINITY}}; + [self testStackLayoutSpecWithStyle:style children:@[child1, subnode2] sizeRange:kFixedWidth subnodes:@[subnode1, subnode2] identifier:nil]; +} + +- (void)testAlignCenterWithFlexedMainDimension +{ + ASStackLayoutSpecStyle style = { + .direction = ASStackLayoutDirectionVertical, + .alignItems = ASStackLayoutAlignItemsCenter + }; + + ASStaticSizeDisplayNode *subnode1 = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + subnode1.staticSize = {100, 100}; + subnode1.flexShrink = YES; + + ASStaticSizeDisplayNode *subnode2 = ASDisplayNodeWithBackgroundColor([UIColor blueColor]); + subnode2.staticSize = {50, 50}; + subnode2.flexShrink = YES; + + NSArray *subnodes = @[subnode1, subnode2]; + static ASSizeRange kFixedWidth = {{150, 0}, {150, 100}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kFixedWidth subnodes:subnodes identifier:nil]; +} + +- (void)testAlignCenterWithIndefiniteCrossDimension +{ + ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; + + ASStaticSizeDisplayNode *subnode1 = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + subnode1.staticSize = {100, 100}; + + ASStaticSizeDisplayNode *subnode2 = ASDisplayNodeWithBackgroundColor([UIColor blueColor]); + subnode2.staticSize = {50, 50}; + subnode2.alignSelf = ASStackLayoutAlignSelfCenter; + + NSArray *subnodes = @[subnode1, subnode2]; + static ASSizeRange kFixedWidth = {{150, 0}, {150, INFINITY}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kFixedWidth subnodes:subnodes identifier:nil]; +} + +- (void)testAlignedStart +{ + ASStackLayoutSpecStyle style = { + .direction = ASStackLayoutDirectionVertical, + .justifyContent = ASStackLayoutJustifyContentCenter, + .alignItems = ASStackLayoutAlignItemsStart + }; + + NSArray *subnodes = defaultSubnodes(); + ((ASStaticSizeDisplayNode *)subnodes[0]).staticSize = {50, 50}; + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {100, 70}; + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {150, 90}; + + ((ASStaticSizeDisplayNode *)subnodes[0]).spacingBefore = 0; + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingBefore = 20; + ((ASStaticSizeDisplayNode *)subnodes[2]).spacingBefore = 30; + + static ASSizeRange kExactSize = {{300, 300}, {300, 300}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kExactSize subnodes:subnodes identifier:nil]; +} + +- (void)testAlignedEnd +{ + ASStackLayoutSpecStyle style = { + .direction = ASStackLayoutDirectionVertical, + .justifyContent = ASStackLayoutJustifyContentCenter, + .alignItems = ASStackLayoutAlignItemsEnd + }; + + NSArray *subnodes = defaultSubnodes(); + ((ASStaticSizeDisplayNode *)subnodes[0]).staticSize = {50, 50}; + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {100, 70}; + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {150, 90}; + + ((ASStaticSizeDisplayNode *)subnodes[0]).spacingBefore = 0; + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingBefore = 20; + ((ASStaticSizeDisplayNode *)subnodes[2]).spacingBefore = 30; + + static ASSizeRange kExactSize = {{300, 300}, {300, 300}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kExactSize subnodes:subnodes identifier:nil]; +} + +- (void)testAlignedCenter +{ + ASStackLayoutSpecStyle style = { + .direction = ASStackLayoutDirectionVertical, + .justifyContent = ASStackLayoutJustifyContentCenter, + .alignItems = ASStackLayoutAlignItemsCenter + }; + + NSArray *subnodes = defaultSubnodes(); + ((ASStaticSizeDisplayNode *)subnodes[0]).staticSize = {50, 50}; + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {100, 70}; + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {150, 90}; + + ((ASStaticSizeDisplayNode *)subnodes[0]).spacingBefore = 0; + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingBefore = 20; + ((ASStaticSizeDisplayNode *)subnodes[2]).spacingBefore = 30; + + static ASSizeRange kExactSize = {{300, 300}, {300, 300}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kExactSize subnodes:subnodes identifier:nil]; +} + +- (void)testAlignedStretchNoChildExceedsMin +{ + ASStackLayoutSpecStyle style = { + .direction = ASStackLayoutDirectionVertical, + .justifyContent = ASStackLayoutJustifyContentCenter, + .alignItems = ASStackLayoutAlignItemsStretch + }; + + NSArray *subnodes = defaultSubnodes(); + ((ASStaticSizeDisplayNode *)subnodes[0]).staticSize = {50, 50}; + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {100, 70}; + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {150, 90}; + + ((ASStaticSizeDisplayNode *)subnodes[0]).spacingBefore = 0; + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingBefore = 20; + ((ASStaticSizeDisplayNode *)subnodes[2]).spacingBefore = 30; + + static ASSizeRange kVariableSize = {{200, 200}, {300, 300}}; + // all children should be 200px wide + [self testStackLayoutSpecWithStyle:style sizeRange:kVariableSize subnodes:subnodes identifier:nil]; +} + +- (void)testAlignedStretchOneChildExceedsMin +{ + ASStackLayoutSpecStyle style = { + .direction = ASStackLayoutDirectionVertical, + .justifyContent = ASStackLayoutJustifyContentCenter, + .alignItems = ASStackLayoutAlignItemsStretch + }; + + NSArray *subnodes = defaultSubnodes(); + ((ASStaticSizeDisplayNode *)subnodes[0]).staticSize = {50, 50}; + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {100, 70}; + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {150, 90}; + + ((ASStaticSizeDisplayNode *)subnodes[0]).spacingBefore = 0; + ((ASStaticSizeDisplayNode *)subnodes[1]).spacingBefore = 20; + ((ASStaticSizeDisplayNode *)subnodes[2]).spacingBefore = 30; + + static ASSizeRange kVariableSize = {{50, 50}, {300, 300}}; + // all children should be 150px wide + [self testStackLayoutSpecWithStyle:style sizeRange:kVariableSize subnodes:subnodes identifier:nil]; +} + +- (void)testEmptyStack +{ + static ASSizeRange kVariableSize = {{50, 50}, {300, 300}}; + [self testStackLayoutSpecWithStyle:{} sizeRange:kVariableSize subnodes:@[] identifier:nil]; +} + +- (void)testFixedFlexBasisAppliedWhenFlexingItems +{ + ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; + + NSArray *subnodes = defaultSubnodesWithSameSize({50, 50}, NO); + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {150, 150}; + + for (ASStaticSizeDisplayNode *subnode in subnodes) { + subnode.flexGrow = YES; + subnode.flexBasis = ASRelativeDimensionMakeWithPoints(10); + } + + // width 300px; height 0-150px. + static ASSizeRange kUnderflowSize = {{300, 0}, {300, 150}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kUnderflowSize subnodes:subnodes identifier:@"underflow"]; + + // width 200px; height 0-150px. + static ASSizeRange kOverflowSize = {{200, 0}, {200, 150}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kOverflowSize subnodes:subnodes identifier:@"overflow"]; +} + +- (void)testPercentageFlexBasisResolvesAgainstParentSize +{ + ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; + + NSArray *subnodes = defaultSubnodesWithSameSize({50, 50}, NO); + + for (ASStaticSizeDisplayNode *subnode in subnodes) { + subnode.flexGrow = YES; + } + + // This should override the intrinsic size of 50pts and instead compute to 50% = 100pts. + // The result should be that the red box is twice as wide as the blue and gree boxes after flexing. + ((ASStaticSizeDisplayNode *)subnodes[0]).flexBasis = ASRelativeDimensionMakeWithPercent(0.5); + + static ASSizeRange kSize = {{200, 0}, {200, INFINITY}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; +} + +- (void)testFixedFlexBasisOverridesIntrinsicSizeForNonFlexingChildren +{ + ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; + + NSArray *subnodes = defaultSubnodes(); + ((ASStaticSizeDisplayNode *)subnodes[0]).staticSize = {50, 50}; + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {150, 150}; + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {50, 50}; + + for (ASStaticSizeDisplayNode *subnode in subnodes) { + subnode.flexBasis = ASRelativeDimensionMakeWithPoints(20); + } + + static ASSizeRange kSize = {{300, 0}, {300, 150}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; +} + +- (void)testCrossAxisStretchingOccursAfterStackAxisFlexing +{ + NSArray *subnodes = @[ + ASDisplayNodeWithBackgroundColor([UIColor greenColor]), + ASDisplayNodeWithBackgroundColor([UIColor blueColor]), + ASDisplayNodeWithBackgroundColor([UIColor redColor]) + ]; + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {10, 0}; + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {3000, 3000}; + + ASRatioLayoutSpec *child2 = [ASRatioLayoutSpec newWithRatio:1.0 child:subnodes[2]]; + child2.flexGrow = YES; + child2.flexShrink = YES; + + // If cross axis stretching occurred *before* flexing, then the blue child would be stretched to 3000 points tall. + // Instead it should be stretched to 300 points tall, matching the red child and not overlapping the green inset. + ASLayoutSpec *layoutSpec = + [ASBackgroundLayoutSpec + newWithChild: + [ASInsetLayoutSpec + newWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) + child: + [ASStackLayoutSpec + newWithStyle:{ + .direction = ASStackLayoutDirectionHorizontal, + .alignItems = ASStackLayoutAlignItemsStretch, + } + children:@[subnodes[1], child2,]]] + background:subnodes[0]]; + + static ASSizeRange kSize = {{300, 0}, {300, INFINITY}}; + [self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:subnodes identifier:nil]; +} + +- (void)testViolationIsDistributedEquallyAmongFlexibleChildren +{ + ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; + + NSArray *subnodes = defaultSubnodes(); + + ((ASStaticSizeDisplayNode *)subnodes[0]).staticSize = {300, 50}; + ((ASStaticSizeDisplayNode *)subnodes[0]).flexShrink = YES; + + ((ASStaticSizeDisplayNode *)subnodes[1]).staticSize = {100, 50}; + ((ASStaticSizeDisplayNode *)subnodes[1]).flexShrink = NO; + + ((ASStaticSizeDisplayNode *)subnodes[2]).staticSize = {200, 50}; + ((ASStaticSizeDisplayNode *)subnodes[2]).flexShrink = YES; + + // A width of 400px results in a violation of 200px. This is distributed equally among each flexible child, + // causing both of them to be shrunk by 100px, resulting in widths of 300px, 100px, and 50px. + // In the W3 flexbox standard, flexible children are shrunk proportionate to their original sizes, + // resulting in widths of 180px, 100px, and 120px. + // This test verifies the current behavior--the snapshot contains widths 300px, 100px, and 50px. + static ASSizeRange kSize = {{400, 0}, {400, 150}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; +} + +@end diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index 6275fc3370..08531ce131 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -10,6 +10,10 @@ #import "ASTableView.h" +#define NumberOfSections 10 +#define NumberOfRowsPerSection 20 +#define NumberOfReloadIterations 50 + @interface ASTestTableView : ASTableView @property (atomic, copy) void (^willDeallocBlock)(ASTableView *tableView); @end @@ -52,6 +56,32 @@ @end +@interface ASTableViewFilledDataSource : NSObject + +@end + +@implementation ASTableViewFilledDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return NumberOfSections; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return NumberOfRowsPerSection; +} + +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + ASTextCellNode *textCellNode = [ASTextCellNode new]; + textCellNode.text = indexPath.description; + + return textCellNode; +} + +@end + @interface ASTableViewTests : XCTestCase @end @@ -60,27 +90,99 @@ - (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate { ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; - + __block BOOL tableViewDidDealloc = NO; tableView.willDeallocBlock = ^(ASTableView *v){ tableViewDidDealloc = YES; }; - + ASTableViewTestDelegate *delegate = [[ASTableViewTestDelegate alloc] init]; - + __block BOOL delegateDidDealloc = NO; delegate.willDeallocBlock = ^(ASTableViewTestDelegate *d){ delegateDidDealloc = YES; }; - + tableView.asyncDataSource = delegate; tableView.asyncDelegate = delegate; - + [delegate release]; XCTAssertTrue(delegateDidDealloc, @"unexpected delegate lifetime:%@", delegate); - + XCTAssertNoThrow([tableView release], @"unexpected exception when deallocating table view:%@", tableView); XCTAssertTrue(tableViewDidDealloc, @"unexpected table view lifetime:%@", tableView); } +- (NSIndexSet *)randomIndexSet +{ + NSInteger randA = arc4random_uniform(NumberOfSections - 1); + NSInteger randB = arc4random_uniform(NumberOfSections - 1); + + return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(MIN(randA, randB), MAX(randA, randB) - MIN(randA, randB))]; +} + +- (NSArray *)randomIndexPathsExisting:(BOOL)existing +{ + NSMutableArray *indexPaths = [NSMutableArray array]; + [[self randomIndexSet] enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + NSUInteger rowNum = NumberOfRowsPerSection; + NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; + for (NSUInteger i = (existing ? 0 : rowNum); i < (existing ? rowNum : rowNum * 2); i++) { + // Maximize evility by sporadically skipping indicies 1/3rd of the time, but only if reloading existing rows + if (existing && arc4random_uniform(2) == 0) { + continue; + } + + NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; + [indexPaths addObject:indexPath]; + } + }]; + return indexPaths; +} + +- (void)testReloadData +{ + // Keep the viewport moderately sized so that new cells are loaded on scrolling + ASTableView *tableView = [[ASTableView alloc] initWithFrame:CGRectMake(0, 0, 100, 500) + style:UITableViewStylePlain + asyncDataFetching:YES]; + + ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; + + tableView.asyncDelegate = dataSource; + tableView.asyncDataSource = dataSource; + + [tableView reloadData]; + + for (int i = 0; i < NumberOfReloadIterations; ++i) { + UITableViewRowAnimation rowAnimation = (arc4random_uniform(1) == 0 ? UITableViewRowAnimationMiddle : UITableViewRowAnimationNone); + + BOOL animatedScroll = (arc4random_uniform(1) == 0 ? YES : NO); + BOOL reloadRowsInsteadOfSections = (arc4random_uniform(1) == 0 ? YES : NO); + BOOL letRunloopProceed = (arc4random_uniform(1) == 0 ? YES : NO); + BOOL useBeginEndUpdates = (arc4random_uniform(2) == 0 ? YES : NO); + + if (useBeginEndUpdates) { + [tableView beginUpdates]; + } + + if (reloadRowsInsteadOfSections) { + [tableView reloadRowsAtIndexPaths:[self randomIndexPathsExisting:YES] withRowAnimation:rowAnimation]; + } else { + [tableView reloadSections:[self randomIndexSet] withRowAnimation:rowAnimation]; + } + + [tableView setContentOffset:CGPointMake(0, arc4random_uniform(tableView.contentSize.height - tableView.bounds.size.height)) animated:animatedScroll]; + + if (letRunloopProceed) { + // Run other stuff on the main queue for between 2ms and 1000ms. + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:(1 / (1 + arc4random_uniform(500)))]]; + } + + if (useBeginEndUpdates) { + [tableView endUpdates]; + } + } +} + @end diff --git a/AsyncDisplayKitTests/ASTextNodeRendererTests.m b/AsyncDisplayKitTests/ASTextNodeRendererTests.m index fa210c12c1..a9bf427af4 100644 --- a/AsyncDisplayKitTests/ASTextNodeRendererTests.m +++ b/AsyncDisplayKitTests/ASTextNodeRendererTests.m @@ -22,6 +22,7 @@ @property (nonatomic, readwrite, assign) CGFloat lineSpacing; @property (nonatomic, readwrite, assign) CGSize constrainedSize; +@property (nonatomic, readwrite) NSArray *exclusionPaths; @end @@ -43,6 +44,8 @@ _attributedString = [[NSAttributedString alloc] initWithString:@"Lorem ipsum" attributes:attributes]; _truncationString = [[NSAttributedString alloc] initWithString:@"More"]; + _exclusionPaths = nil; + _constrainedSize = CGSizeMake(FLT_MAX, FLT_MAX); } @@ -52,6 +55,7 @@ truncationString:_truncationString truncationMode:_truncationMode maximumLineCount:_maximumLineCount + exclusionPaths:_exclusionPaths constrainedSize:_constrainedSize]; } @@ -141,4 +145,18 @@ }]; } +- (void)testExclusionPaths +{ + _constrainedSize = CGSizeMake(200, CGFLOAT_MAX); + [self setUpRenderer]; + CGSize sizeWithoutExclusionPath = [_renderer size]; + + CGRect exclusionRect = CGRectMake(20, 0, 180, _lineSpacing * 2.0); + _exclusionPaths = @[[UIBezierPath bezierPathWithRect:exclusionRect]]; + [self setUpRenderer]; + CGSize sizeWithExclusionPath = [_renderer size]; + + XCTAssertEqualWithAccuracy(sizeWithoutExclusionPath.height + exclusionRect.size.height, sizeWithExclusionPath.height, 0.5, @"Using an exclusion path so the the text can not fit into the first two lines should increment the size of the text by the heigth of the exclusion path"); +} + @end diff --git a/AsyncDisplayKitTests/ASTextNodeTests.m b/AsyncDisplayKitTests/ASTextNodeTests.m index 6ea99d792a..c4f0789aa1 100644 --- a/AsyncDisplayKitTests/ASTextNodeTests.m +++ b/AsyncDisplayKitTests/ASTextNodeTests.m @@ -177,4 +177,23 @@ XCTAssertFalse(delegate.tappedLinkValue, @"Expected the delegate to be told that the value %@ was tapped, instead it thinks the tapped attribute value is %@", linkAttributeValue, delegate.tappedLinkValue); } +#pragma mark exclusion Paths + +- (void)testSettingExclusionPaths +{ + NSArray *exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(10, 20, 30, 40)]]; + _textNode.exclusionPaths = exclusionPaths; + XCTAssertTrue([_textNode.exclusionPaths isEqualToArray:exclusionPaths], @"Failed to set exclusion paths"); +} + +- (void)testAddingExclusionPathsShouldInvalidateAndIncreaseTheSize +{ + CGSize constrainedSize = CGSizeMake(100, CGFLOAT_MAX); + CGSize sizeWithoutExclusionPaths = [_textNode measure:constrainedSize]; + _textNode.exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(50, 20, 30, 40)]]; + CGSize sizeWithExclusionPaths = [_textNode measure:constrainedSize]; + + XCTAssertGreaterThan(sizeWithExclusionPaths.height, sizeWithoutExclusionPaths.height, @"Setting exclusions paths should invalidate the calculated size and return a greater size"); +} + @end diff --git a/AsyncDisplayKitTests/ReferenceImages/ASImageNodeSnapshotTests/testRenderLogoSquare@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASImageNodeSnapshotTests/testRenderLogoSquare@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages/ASImageNodeSnapshotTests/testRenderLogoSquare@2x.png rename to AsyncDisplayKitTests/ReferenceImages_32/ASImageNodeSnapshotTests/testRenderLogoSquare@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotCentering@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotCentering@2x.png new file mode 100644 index 0000000000..02717f8fdb Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotCentering@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions@2x.png new file mode 100644 index 0000000000..50cb613c24 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringX@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringX@2x.png new file mode 100644 index 0000000000..69e7392384 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringX@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringXCenteringY@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringXCenteringY@2x.png new file mode 100644 index 0000000000..311ef9ed32 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringXCenteringY@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringY@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringY@2x.png new file mode 100644 index 0000000000..28036afad5 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringY@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions@2x.png new file mode 100644 index 0000000000..50cb613c24 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumX@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumX@2x.png new file mode 100644 index 0000000000..270b15feb6 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumX@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumXSizingMinimumY@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumXSizingMinimumY@2x.png new file mode 100644 index 0000000000..7fddbff94e Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumXSizingMinimumY@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumY@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumY@2x.png new file mode 100644 index 0000000000..b14c267b42 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumY@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-10@2x.png new file mode 100644 index 0000000000..ec0a4cf3c7 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-inf@2x.png new file mode 100644 index 0000000000..fb4775c0f5 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-10@2x.png new file mode 100644 index 0000000000..993d4c0592 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-inf@2x.png new file mode 100644 index 0000000000..d0b2cbd912 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-10@2x.png new file mode 100644 index 0000000000..537326b3db Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-inf@2x.png new file mode 100644 index 0000000000..b38c9ccb1c Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-10@2x.png new file mode 100644 index 0000000000..797e7ac410 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-inf@2x.png new file mode 100644 index 0000000000..8c88149380 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-10@2x.png new file mode 100644 index 0000000000..f9decf0282 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-inf@2x.png new file mode 100644 index 0000000000..e3efdda0a1 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-10@2x.png new file mode 100644 index 0000000000..f494863bc7 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-inf@2x.png new file mode 100644 index 0000000000..5653f7f6fc Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-10@2x.png new file mode 100644 index 0000000000..5fa79d8a92 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-inf@2x.png new file mode 100644 index 0000000000..bece9190a6 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-10@2x.png new file mode 100644 index 0000000000..e8132c50b9 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-inf@2x.png new file mode 100644 index 0000000000..6da1b998c0 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-0@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-0@2x.png new file mode 100644 index 0000000000..5104ab98be Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-0@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-inf@2x.png new file mode 100644 index 0000000000..8ac22fa361 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-0@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-0@2x.png new file mode 100644 index 0000000000..2054fac7ea Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-0@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-inf@2x.png new file mode 100644 index 0000000000..d09eb720c8 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-0@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-0@2x.png new file mode 100644 index 0000000000..8fe02d548d Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-0@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-inf@2x.png new file mode 100644 index 0000000000..21f8ad6169 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-0@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-0@2x.png new file mode 100644 index 0000000000..a77ce6a0c9 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-0@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-inf@2x.png new file mode 100644 index 0000000000..c4e1791769 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-0@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-0@2x.png new file mode 100644 index 0000000000..19f457874e Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-0@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-inf@2x.png new file mode 100644 index 0000000000..81a07b2570 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-0@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-0@2x.png new file mode 100644 index 0000000000..31422ea5d7 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-0@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-inf@2x.png new file mode 100644 index 0000000000..63cff77a6f Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-0@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-0@2x.png new file mode 100644 index 0000000000..1c55fe57ed Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-0@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-inf@2x.png new file mode 100644 index 0000000000..5d0bfee89e Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-0@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-0@2x.png new file mode 100644 index 0000000000..2b6430c5b5 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-0@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-inf@2x.png new file mode 100644 index 0000000000..6da1b998c0 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-10@2x.png new file mode 100644 index 0000000000..c9255d62e8 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-inf@2x.png new file mode 100644 index 0000000000..db62499472 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-10@2x.png new file mode 100644 index 0000000000..2b052c4e38 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-inf@2x.png new file mode 100644 index 0000000000..d0b2cbd912 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-10@2x.png new file mode 100644 index 0000000000..69368ee671 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-inf@2x.png new file mode 100644 index 0000000000..55efcf5dba Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-10@2x.png new file mode 100644 index 0000000000..797e7ac410 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-inf@2x.png new file mode 100644 index 0000000000..8c88149380 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-10@2x.png new file mode 100644 index 0000000000..01411e3f2c Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-inf@2x.png new file mode 100644 index 0000000000..e3efdda0a1 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-10@2x.png new file mode 100644 index 0000000000..f0cd235628 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-inf@2x.png new file mode 100644 index 0000000000..5653f7f6fc Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-10@2x.png new file mode 100644 index 0000000000..5fa79d8a92 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-inf@2x.png new file mode 100644 index 0000000000..bece9190a6 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-10@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-10@2x.png new file mode 100644 index 0000000000..e8132c50b9 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-10@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-inf@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-inf@2x.png new file mode 100644 index 0000000000..6da1b998c0 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-inf@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASOverlayLayoutSpecSnapshotTests/testOverlay@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASOverlayLayoutSpecSnapshotTests/testOverlay@2x.png new file mode 100644 index 0000000000..198e5394cc Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASOverlayLayoutSpecSnapshotTests/testOverlay@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_DoubleRatio@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_DoubleRatio@2x.png new file mode 100644 index 0000000000..a10ef96975 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_DoubleRatio@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_HalfRatio@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_HalfRatio@2x.png new file mode 100644 index 0000000000..0cbc831393 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_HalfRatio@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_SevenTimesRatio@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_SevenTimesRatio@2x.png new file mode 100644 index 0000000000..48be1e63a7 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_SevenTimesRatio@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_TenTimesRatioWithItemTooBig@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_TenTimesRatioWithItemTooBig@2x.png new file mode 100644 index 0000000000..ce593f7ad7 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_TenTimesRatioWithItemTooBig@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithFlexedMainDimension@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithFlexedMainDimension@2x.png new file mode 100644 index 0000000000..8c266c5703 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithFlexedMainDimension@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithIndefiniteCrossDimension@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithIndefiniteCrossDimension@2x.png new file mode 100644 index 0000000000..00b3bfb369 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithIndefiniteCrossDimension@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedCenter@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedCenter@2x.png new file mode 100644 index 0000000000..46b73e6a6e Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedCenter@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedEnd@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedEnd@2x.png new file mode 100644 index 0000000000..d2131dd83f Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedEnd@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStart@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStart@2x.png new file mode 100644 index 0000000000..5f9dc090e0 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStart@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchNoChildExceedsMin@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchNoChildExceedsMin@2x.png new file mode 100644 index 0000000000..a349c400ac Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchNoChildExceedsMin@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchOneChildExceedsMin@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchOneChildExceedsMin@2x.png new file mode 100644 index 0000000000..1a31b25613 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchOneChildExceedsMin@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingAfter@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingAfter@2x.png new file mode 100644 index 0000000000..f253af0a20 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingAfter@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBalancedOut@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBalancedOut@2x.png new file mode 100644 index 0000000000..60ffcd81bc Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBalancedOut@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBefore@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBefore@2x.png new file mode 100644 index 0000000000..e74a5b5a28 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBefore@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildThatChangesCrossSizeWhenMainSizeIsFlexed@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildThatChangesCrossSizeWhenMainSizeIsFlexed@2x.png new file mode 100644 index 0000000000..fcb8d934c0 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildThatChangesCrossSizeWhenMainSizeIsFlexed@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_fixedHeight@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_fixedHeight@2x.png new file mode 100644 index 0000000000..435a4cf60e Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_fixedHeight@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_variableHeight@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_variableHeight@2x.png new file mode 100644 index 0000000000..718b60304f Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_variableHeight@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisStretchingOccursAfterStackAxisFlexing@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisStretchingOccursAfterStackAxisFlexing@2x.png new file mode 100644 index 0000000000..3a3b374053 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisStretchingOccursAfterStackAxisFlexing@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testEmptyStack@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testEmptyStack@2x.png new file mode 100644 index 0000000000..94dc9a5d2b Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testEmptyStack@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_overflow@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_overflow@2x.png new file mode 100644 index 0000000000..d224b220fd Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_overflow@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_underflow@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_underflow@2x.png new file mode 100644 index 0000000000..df55d754f1 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_underflow@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisOverridesIntrinsicSizeForNonFlexingChildren@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisOverridesIntrinsicSizeForNonFlexingChildren@2x.png new file mode 100644 index 0000000000..64048bbd05 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisOverridesIntrinsicSizeForNonFlexingChildren@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_overflow@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_overflow@2x.png new file mode 100644 index 0000000000..9192066425 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_overflow@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_underflow@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_underflow@2x.png new file mode 100644 index 0000000000..54929c089c Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_underflow@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedCenterWithChildSpacing_variableHeight@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedCenterWithChildSpacing_variableHeight@2x.png new file mode 100644 index 0000000000..46dab172c1 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedCenterWithChildSpacing_variableHeight@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists@2x.png new file mode 100644 index 0000000000..0471d0cc08 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_flex@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_flex@2x.png new file mode 100644 index 0000000000..6c7985ff0e Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_flex@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyCenter@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyCenter@2x.png new file mode 100644 index 0000000000..07bf66b685 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyCenter@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyEnd@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyEnd@2x.png new file mode 100644 index 0000000000..a68c624868 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyEnd@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyStart@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyStart@2x.png new file mode 100644 index 0000000000..e078398440 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyStart@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPercentageFlexBasisResolvesAgainstParentSize@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPercentageFlexBasisResolvesAgainstParentSize@2x.png new file mode 100644 index 0000000000..eba4df4df3 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPercentageFlexBasisResolvesAgainstParentSize@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacingWithChildrenHavingNilObjects_variableHeight@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacingWithChildrenHavingNilObjects_variableHeight@2x.png new file mode 100644 index 0000000000..ca249a5daa Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacingWithChildrenHavingNilObjects_variableHeight@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacing_variableHeight@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacing_variableHeight@2x.png new file mode 100644 index 0000000000..77d64be4cb Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacing_variableHeight@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_flex@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_flex@2x.png new file mode 100644 index 0000000000..89dac5e442 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_flex@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyCenter@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyCenter@2x.png new file mode 100644 index 0000000000..2dfbc80025 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyCenter@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyEnd@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyEnd@2x.png new file mode 100644 index 0000000000..6fb41b79f8 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyEnd@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyStart@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyStart@2x.png new file mode 100644 index 0000000000..4be46ae87f Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyStart@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testViolationIsDistributedEquallyAmongFlexibleChildren@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testViolationIsDistributedEquallyAmongFlexibleChildren@2x.png new file mode 100644 index 0000000000..46598b8411 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testViolationIsDistributedEquallyAmongFlexibleChildren@2x.png differ diff --git a/Base/ASAssert.h b/Base/ASAssert.h index 84fed6977f..b0b53cc855 100644 --- a/Base/ASAssert.h +++ b/Base/ASAssert.h @@ -47,3 +47,10 @@ #define ASDisplayNodeFailAssert(description, ...) ASDisplayNodeAssertWithSignal(NO, nil, (description), ##__VA_ARGS__) #define ASDisplayNodeCFailAssert(description, ...) ASDisplayNodeCAssertWithSignal(NO, nil, (description), ##__VA_ARGS__) + +#define ASDisplayNodeConditionalAssert(shouldTestCondition, condition, description, ...) ASDisplayNodeAssert((!(shouldTestCondition) || (condition)), nil, (description), ##__VA_ARGS__) +#define ASDisplayNodeConditionalCAssert(shouldTestCondition, condition, description, ...) ASDisplayNodeCAssert((!(shouldTestCondition) || (condition)), nil, (description), ##__VA_ARGS__) + +#define ASDisplayNodeCAssertPositiveReal(description, num) ASDisplayNodeCAssert(num >= 0 && num <= CGFLOAT_MAX, @"%@ must be a real positive integer.", description) +#define ASDisplayNodeCAssertInfOrPositiveReal(description, num) ASDisplayNodeCAssert(isinf(num) || (num >= 0 && num <= CGFLOAT_MAX), @"%@ must be infinite or a real positive integer.", description) + diff --git a/Default-568h@2x.png b/Default-568h@2x.png new file mode 100644 index 0000000000..0891b7aabf Binary files /dev/null and b/Default-568h@2x.png differ diff --git a/Podfile.lock b/Podfile.lock index ca9c28e04f..4196f05616 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - FBSnapshotTestCase (1.5) + - FBSnapshotTestCase (1.8.1) - OCMock (2.2.4) DEPENDENCIES: @@ -7,7 +7,7 @@ DEPENDENCIES: - OCMock (~> 2.2) SPEC CHECKSUMS: - FBSnapshotTestCase: 26f32d8fa9eb30e9f09712ecfb097808bc79b898 + FBSnapshotTestCase: 3dc3899168747a0319c5278f5b3445c13a6532dd OCMock: a6a7dc0e3997fb9f35d99f72528698ebf60d64f2 -COCOAPODS: 0.36.4 +COCOAPODS: 0.37.2 diff --git a/examples/ASTableViewStressTest/Default-568h@2x.png b/examples/ASTableViewStressTest/Default-568h@2x.png new file mode 100644 index 0000000000..6ee80b9393 Binary files /dev/null and b/examples/ASTableViewStressTest/Default-568h@2x.png differ diff --git a/examples/ASTableViewStressTest/Default-667h@2x.png b/examples/ASTableViewStressTest/Default-667h@2x.png new file mode 100644 index 0000000000..e7b975e21b Binary files /dev/null and b/examples/ASTableViewStressTest/Default-667h@2x.png differ diff --git a/examples/ASTableViewStressTest/Default-736h@3x.png b/examples/ASTableViewStressTest/Default-736h@3x.png new file mode 100644 index 0000000000..c8949cae16 Binary files /dev/null and b/examples/ASTableViewStressTest/Default-736h@3x.png differ diff --git a/examples/ASTableViewStressTest/Podfile b/examples/ASTableViewStressTest/Podfile new file mode 100644 index 0000000000..6c012e3c04 --- /dev/null +++ b/examples/ASTableViewStressTest/Podfile @@ -0,0 +1,3 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj b/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..7ae0001200 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,344 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 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 = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; +/* 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 = ""; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 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 = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */, + 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* 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 = ( + 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)"; + }; + 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)"; + }; + 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 */; +} diff --git a/examples/ASTableViewStressTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/ASTableViewStressTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/ASTableViewStressTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/ASTableViewStressTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..5c91bfc64d --- /dev/null +++ b/examples/ASTableViewStressTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..d98549fd35 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/ASTableViewStressTest/Sample/AppDelegate.h b/examples/ASTableViewStressTest/Sample/AppDelegate.h new file mode 100644 index 0000000000..2aa29369b4 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/AppDelegate.h @@ -0,0 +1,18 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/examples/ASTableViewStressTest/Sample/AppDelegate.m b/examples/ASTableViewStressTest/Sample/AppDelegate.m new file mode 100644 index 0000000000..a8e5594780 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/AppDelegate.m @@ -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 = [[ViewController alloc] init]; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/ASTableViewStressTest/Sample/Info.plist b/examples/ASTableViewStressTest/Sample/Info.plist new file mode 100644 index 0000000000..ad825d6e33 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + org.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/ASTableViewStressTest/Sample/ViewController.h b/examples/ASTableViewStressTest/Sample/ViewController.h new file mode 100644 index 0000000000..d0e9200d88 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/ViewController.h @@ -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 + +@interface ViewController : UIViewController + +@end diff --git a/examples/ASTableViewStressTest/Sample/ViewController.m b/examples/ASTableViewStressTest/Sample/ViewController.m new file mode 100644 index 0000000000..92e3ce7e33 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/ViewController.m @@ -0,0 +1,176 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import "ViewController.h" + +#import +#import + +#define NumberOfSections 10 +#define NumberOfRowsPerSection 20 +#define NumberOfReloadIterations 500 + +@interface ViewController () +{ + ASTableView *_tableView; + NSMutableArray *_sections; // Contains arrays of indexPaths representing rows +} + +@end + + +@implementation ViewController + +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + _tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain asyncDataFetching:YES]; + _tableView.asyncDataSource = self; + _tableView.asyncDelegate = self; + _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + + _sections = [NSMutableArray arrayWithCapacity:NumberOfSections]; + for (int i = 0; i < NumberOfSections; i++) { + NSMutableArray *rowsArray = [NSMutableArray arrayWithCapacity:NumberOfRowsPerSection]; + for (int j = 0; j < NumberOfRowsPerSection; j++) { + [rowsArray addObject:[NSIndexPath indexPathForRow:j inSection:i]]; + } + [_sections addObject:rowsArray]; + } + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self.view addSubview:_tableView]; +} + +- (void)viewWillLayoutSubviews +{ + _tableView.frame = self.view.bounds; +} + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + + [self thrashTableView]; +} + +- (NSIndexSet *)randomIndexSet +{ + u_int32_t upperBound = (u_int32_t)_sections.count - 1; + u_int32_t randA = arc4random_uniform(upperBound); + u_int32_t randB = arc4random_uniform(upperBound); + + return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(MIN(randA, randB), MAX(randA, randB) - MIN(randA, randB))]; +} + +- (NSArray *)randomIndexPathsExisting:(BOOL)existing +{ + NSMutableArray *indexPaths = [NSMutableArray array]; + [[self randomIndexSet] enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + NSUInteger rowNum = [self tableView:_tableView numberOfRowsInSection:idx]; + NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; + for (NSUInteger i = (existing ? 0 : rowNum); i < (existing ? rowNum : rowNum * 2); i++) { + // Maximize evility by sporadically skipping indicies 1/3rd of the time, but only if reloading existing rows + if (existing && arc4random_uniform(2) == 0) { + continue; + } + + NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; + [indexPaths addObject:indexPath]; + } + }]; + return indexPaths; +} + +- (void)thrashTableView +{ + _tableView.asyncDelegate = self; + _tableView.asyncDataSource = self; + + [_tableView reloadData]; + + NSArray *indexPathsAddedAndRemoved = nil; + + for (int i = 0; i < NumberOfReloadIterations; ++i) { + UITableViewRowAnimation rowAnimation = (arc4random_uniform(1) == 0 ? UITableViewRowAnimationMiddle : UITableViewRowAnimationNone); + + BOOL animatedScroll = (arc4random_uniform(1) == 0 ? YES : NO); + BOOL reloadRowsInsteadOfSections = (arc4random_uniform(1) == 0 ? YES : NO); + BOOL letRunloopProceed = (arc4random_uniform(1) == 0 ? YES : NO); + BOOL addIndexPaths = (arc4random_uniform(1) == 0 ? YES : NO); + BOOL useBeginEndUpdates = (arc4random_uniform(2) == 0 ? YES : NO); + + if (useBeginEndUpdates) { + [_tableView beginUpdates]; + } + + if (reloadRowsInsteadOfSections) { + [_tableView reloadRowsAtIndexPaths:[self randomIndexPathsExisting:YES] withRowAnimation:rowAnimation]; + } else { + [_tableView reloadSections:[self randomIndexSet] withRowAnimation:rowAnimation]; + } + + if (addIndexPaths && !indexPathsAddedAndRemoved) { + indexPathsAddedAndRemoved = [self randomIndexPathsExisting:NO]; + for (NSIndexPath *indexPath in indexPathsAddedAndRemoved) { + [_sections[indexPath.section] addObject:indexPath]; + } + [_tableView insertRowsAtIndexPaths:indexPathsAddedAndRemoved withRowAnimation:rowAnimation]; + } + + [_tableView setContentOffset:CGPointMake(0, arc4random_uniform(_tableView.contentSize.height - _tableView.bounds.size.height)) animated:animatedScroll]; + + if (letRunloopProceed) { + // Run other stuff on the main queue for between 2ms and 1000ms. + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:(1 / (1 + arc4random_uniform(500)))]]; + + if (indexPathsAddedAndRemoved) { + for (NSIndexPath *indexPath in indexPathsAddedAndRemoved) { + [_sections[indexPath.section] removeObjectIdenticalTo:indexPath]; + } + [_tableView deleteRowsAtIndexPaths:indexPathsAddedAndRemoved withRowAnimation:rowAnimation]; + indexPathsAddedAndRemoved = nil; + } + } + + if (useBeginEndUpdates) { + [_tableView endUpdates]; + } + } +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return _sections.count; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return [(NSArray *)[_sections objectAtIndex:section] count]; +} + +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + ASTextCellNode *textCellNode = [ASTextCellNode new]; + textCellNode.text = indexPath.description; + + return textCellNode; +} + +@end diff --git a/examples/ASTableViewStressTest/Sample/main.m b/examples/ASTableViewStressTest/Sample/main.m new file mode 100644 index 0000000000..ae9488711c --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/main.m @@ -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 + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/Kittens/Sample.xcodeproj/project.pbxproj b/examples/Kittens/Sample.xcodeproj/project.pbxproj index 5b0e3eb29c..fde58763d9 100644 --- a/examples/Kittens/Sample.xcodeproj/project.pbxproj +++ b/examples/Kittens/Sample.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 05561CFA19D4E77700CBA93C /* BlurbNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 05561CF919D4E77700CBA93C /* BlurbNode.m */; }; - 05561CFD19D4F94A00CBA93C /* KittenNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 05561CFC19D4F94A00CBA93C /* KittenNode.m */; }; + 05561CFD19D4F94A00CBA93C /* KittenNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05561CFC19D4F94A00CBA93C /* KittenNode.mm */; }; 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; @@ -22,7 +22,7 @@ 05561CF819D4E77700CBA93C /* BlurbNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlurbNode.h; sourceTree = ""; }; 05561CF919D4E77700CBA93C /* BlurbNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlurbNode.m; sourceTree = ""; }; 05561CFB19D4F94A00CBA93C /* KittenNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KittenNode.h; sourceTree = ""; }; - 05561CFC19D4F94A00CBA93C /* KittenNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KittenNode.m; sourceTree = ""; }; + 05561CFC19D4F94A00CBA93C /* KittenNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KittenNode.mm; sourceTree = ""; }; 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; 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 = ""; }; @@ -76,7 +76,7 @@ 05E2128B19D4DB510098F589 /* ViewController.h */, 05E2128C19D4DB510098F589 /* ViewController.m */, 05561CFB19D4F94A00CBA93C /* KittenNode.h */, - 05561CFC19D4F94A00CBA93C /* KittenNode.m */, + 05561CFC19D4F94A00CBA93C /* KittenNode.mm */, 05561CF819D4E77700CBA93C /* BlurbNode.h */, 05561CF919D4E77700CBA93C /* BlurbNode.m */, 05E2128419D4DB510098F589 /* Supporting Files */, @@ -218,7 +218,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 05561CFD19D4F94A00CBA93C /* KittenNode.m in Sources */, + 05561CFD19D4F94A00CBA93C /* KittenNode.mm in Sources */, 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, 05561CFA19D4E77700CBA93C /* BlurbNode.m in Sources */, diff --git a/examples/Kittens/Sample/AppDelegate.h b/examples/Kittens/Sample/AppDelegate.h index 2aa29369b4..85855277e9 100644 --- a/examples/Kittens/Sample/AppDelegate.h +++ b/examples/Kittens/Sample/AppDelegate.h @@ -11,6 +11,8 @@ #import +#define UseAutomaticLayout 1 + @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; diff --git a/examples/Kittens/Sample/BlurbNode.m b/examples/Kittens/Sample/BlurbNode.m index b67e0553e6..c26cecacad 100644 --- a/examples/Kittens/Sample/BlurbNode.m +++ b/examples/Kittens/Sample/BlurbNode.m @@ -10,10 +10,13 @@ */ #import "BlurbNode.h" +#import "AppDelegate.h" #import #import +#import +#import static CGFloat kTextPadding = 10.0f; static NSString *kLinkAttributeName = @"PlaceKittenNodeLinkAttributeName"; @@ -28,6 +31,9 @@ static NSString *kLinkAttributeName = @"PlaceKittenNodeLinkAttributeName"; @implementation BlurbNode +#pragma mark - +#pragma mark ASCellNode. + - (instancetype)init { if (!(self = [super init])) @@ -67,6 +73,19 @@ static NSString *kLinkAttributeName = @"PlaceKittenNodeLinkAttributeName"; [super didLoad]; } +#if UseAutomaticLayout +- (id)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + return + [ASInsetLayoutSpec + newWithInsets:UIEdgeInsetsMake(kTextPadding, kTextPadding, kTextPadding, kTextPadding) + child: + [ASCenterLayoutSpec + newWithCenteringOptions:ASCenterLayoutSpecCenteringX // Center the text horizontally + sizingOptions:ASCenterLayoutSpecSizingOptionMinimumY // Takes up minimum height + child:_textNode]]; +} +#else - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { // called on a background thread. custom nodes must call -measure: on their subnodes in -calculateSizeThatFits: @@ -84,6 +103,10 @@ static NSString *kLinkAttributeName = @"PlaceKittenNodeLinkAttributeName"; textNodeSize.width, textNodeSize.height); } +#endif + +#pragma mark - +#pragma mark ASTextNodeDelegate methods. - (BOOL)textNode:(ASTextNode *)richTextNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point { diff --git a/examples/Kittens/Sample/KittenNode.m b/examples/Kittens/Sample/KittenNode.mm similarity index 86% rename from examples/Kittens/Sample/KittenNode.m rename to examples/Kittens/Sample/KittenNode.mm index 5a007b186e..db435d40ef 100644 --- a/examples/Kittens/Sample/KittenNode.m +++ b/examples/Kittens/Sample/KittenNode.mm @@ -10,9 +10,13 @@ */ #import "KittenNode.h" +#import "AppDelegate.h" #import +#import +#import + static const CGFloat kImageSize = 80.0f; static const CGFloat kOuterPadding = 16.0f; static const CGFloat kInnerPadding = 10.0f; @@ -124,6 +128,36 @@ static const CGFloat kInnerPadding = 10.0f; NSParagraphStyleAttributeName: style }; } +#if UseAutomaticLayout +- (id)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASRatioLayoutSpec *imagePlaceholder = [ASRatioLayoutSpec newWithRatio:1.0 child:_imageNode]; + imagePlaceholder.flexBasis = ASRelativeDimensionMakeWithPoints(kImageSize); + + _textNode.flexShrink = YES; + + return + [ASInsetLayoutSpec + newWithInsets:UIEdgeInsetsMake(kOuterPadding, kOuterPadding, kOuterPadding, kOuterPadding) + child: + [ASStackLayoutSpec + newWithStyle:{ + .direction = ASStackLayoutDirectionHorizontal, + .spacing = kInnerPadding + } + children:@[imagePlaceholder, _textNode]]]; +} + +// With box model, you don't need to override this method, unless you want to add custom logic. +- (void)layout +{ + [super layout]; + + // Manually layout the divider. + CGFloat pixelHeight = 1.0f / [[UIScreen mainScreen] scale]; + _divider.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, pixelHeight); +} +#else - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { CGSize imageSize = CGSizeMake(kImageSize, kImageSize); @@ -145,5 +179,6 @@ static const CGFloat kInnerPadding = 10.0f; CGSize textSize = _textNode.calculatedSize; _textNode.frame = CGRectMake(kOuterPadding + kImageSize + kInnerPadding, kOuterPadding, textSize.width, textSize.height); } +#endif @end diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index d1a6be51c6..b3293c13fc 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -18,15 +18,15 @@ #import "KittenNode.h" -static const NSInteger kLitterSize = 20; -static const NSInteger kLitterBatchSize = 10; -static const NSInteger kMaxLitterSize = 100; +static const NSInteger kLitterSize = 20; // intial number of kitten cells in ASTableView +static const NSInteger kLitterBatchSize = 10; // number of kitten cells to add to ASTableView +static const NSInteger kMaxLitterSize = 100; // max number of kitten cells allowed in ASTableView @interface ViewController () { ASTableView *_tableView; - // array of boxed CGSizes corresponding to placekitten kittens + // array of boxed CGSizes corresponding to placekitten.com kittens NSArray *_kittenDataSource; BOOL _dataSourceLocked; @@ -54,7 +54,6 @@ static const NSInteger kMaxLitterSize = 100; _tableView.asyncDelegate = self; // populate our "data source" with some random kittens - _kittenDataSource = [self createLitterWithSize:kLitterSize]; return self; @@ -64,10 +63,13 @@ static const NSInteger kMaxLitterSize = 100; { NSMutableArray *kittens = [NSMutableArray arrayWithCapacity:litterSize]; for (NSInteger i = 0; i < litterSize; i++) { + + // placekitten.com will return the same kitten picture if the same pixel height & width are requested, + // so generate kittens with different width & height values. u_int32_t deltaX = arc4random_uniform(10) - 5; u_int32_t deltaY = arc4random_uniform(10) - 5; CGSize size = CGSizeMake(350 + 2 * deltaX, 350 + 4 * deltaY); - + [kittens addObject:[NSValue valueWithCGSize:size]]; } return kittens; @@ -98,7 +100,7 @@ static const NSInteger kMaxLitterSize = 100; #pragma mark - -#pragma mark Kittens. +#pragma mark ASTableView. - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath { @@ -143,17 +145,24 @@ static const NSInteger kMaxLitterSize = 100; - (void)tableView:(UITableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context { NSLog(@"adding kitties"); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); dispatch_async(dispatch_get_main_queue(), ^{ + + // populate a new array of random-sized kittens NSArray *moarKittens = [self createLitterWithSize:kLitterBatchSize]; NSMutableArray *indexPaths = [[NSMutableArray alloc] init]; + + // find number of kittens in the data source and create their indexPaths NSInteger existingRows = _kittenDataSource.count + 1; + for (NSInteger i = 0; i < moarKittens.count; i++) { [indexPaths addObject:[NSIndexPath indexPathForRow:existingRows + i inSection:0]]; } + // add new kittens to the data source & notify table of new indexpaths _kittenDataSource = [_kittenDataSource arrayByAddingObjectsFromArray:moarKittens]; [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; diff --git a/examples/Swift/Sample.xcodeproj/project.pbxproj b/examples/Swift/Sample.xcodeproj/project.pbxproj index 12636b4c1a..4a845e166a 100644 --- a/examples/Swift/Sample.xcodeproj/project.pbxproj +++ b/examples/Swift/Sample.xcodeproj/project.pbxproj @@ -49,7 +49,9 @@ 092C2001FE124604891D6E90 /* Frameworks */, 655F2ABBD991CBDE7140FACE /* Pods */, ); + indentWidth = 2; sourceTree = ""; + tabWidth = 2; }; 050E7C6F19D22E19004363C2 /* Products */ = { isa = PBXGroup; diff --git a/examples/Swift/Sample/AppDelegate.swift b/examples/Swift/Sample/AppDelegate.swift index 3a1dac68c1..a2b00b1727 100644 --- a/examples/Swift/Sample/AppDelegate.swift +++ b/examples/Swift/Sample/AppDelegate.swift @@ -19,7 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { let window = UIWindow(frame: UIScreen.mainScreen().bounds) window.backgroundColor = UIColor.whiteColor() - window.rootViewController = ViewController() + window.rootViewController = ViewController(nibName: nil, bundle: nil) window.makeKeyAndVisible() self.window = window return true diff --git a/examples/Swift/Sample/ViewController.swift b/examples/Swift/Sample/ViewController.swift index 94ebc6e07d..9801a20523 100644 --- a/examples/Swift/Sample/ViewController.swift +++ b/examples/Swift/Sample/ViewController.swift @@ -18,7 +18,7 @@ class ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelega // MARK: UIViewController. - required override init() { + override required init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { self.tableView = ASTableView() super.init(nibName: nil, bundle: nil) @@ -28,7 +28,7 @@ class ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelega } required init(coder aDecoder: NSCoder) { - fatalError("storyboards are incompatible with truth and beauty") + fatalError("storyboards are incompatible with truth and beauty") } override func viewDidLoad() { @@ -50,7 +50,7 @@ class ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelega func tableView(tableView: ASTableView!, nodeForRowAtIndexPath indexPath: NSIndexPath!) -> ASCellNode! { let patter = NSString(format: "[%ld.%ld] says hello!", indexPath.section, indexPath.row) let node = ASTextCellNode() - node.text = patter + node.text = patter as String return node }