diff --git a/.travis.yml b/.travis.yml index fd3259ebdf..69217e9a71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ language: objective-c -osx_image: xcode7.2 +osx_image: xcode7.3 before_install: - brew update - - brew reinstall xctool + - brew reinstall --HEAD xctool + - brew reinstall carthage - gem install cocoapods -v 0.38.2 - gem install slather - xcrun simctl list diff --git a/AsyncDisplayKit-iOS/Info.plist b/AsyncDisplayKit-iOS/Info.plist index 63f8161e4b..d3de8eefb6 100644 --- a/AsyncDisplayKit-iOS/Info.plist +++ b/AsyncDisplayKit-iOS/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 4872c47f84..f094a38b55 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -1,44 +1,47 @@ Pod::Spec.new do |spec| spec.name = 'AsyncDisplayKit' - spec.version = '1.9.6' + spec.version = '1.9.7.2' spec.license = { :type => 'BSD' } spec.homepage = 'http://asyncdisplaykit.org' spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' } spec.summary = 'Smooth asynchronous user interfaces for iOS apps.' - spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.6' } + spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.7.2' } spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/' - spec.public_header_files = [ - 'AsyncDisplayKit/*.h', - 'AsyncDisplayKit/Details/**/*.h', - 'AsyncDisplayKit/Layout/*.h', - 'Base/*.h', - 'AsyncDisplayKit/TextKit/ASTextNodeTypes.h' - ] - - spec.source_files = [ - 'AsyncDisplayKit/**/*.{h,m,mm}', - 'Base/*.{h,m}', - - # Most TextKit components are not public because the C++ content - # in the headers will cause build errors when using - # `use_frameworks!` on 0.39.0 & Swift 2.1. - # See https://github.com/facebook/AsyncDisplayKit/issues/1153 - 'AsyncDisplayKit/TextKit/*.h', - ] - spec.frameworks = 'AssetsLibrary' spec.weak_frameworks = 'Photos','MapKit' - - # ASDealloc2MainObject must be compiled with MRR spec.requires_arc = true - spec.exclude_files = [ - 'AsyncDisplayKit/Details/ASDealloc2MainObject.h', - 'AsyncDisplayKit/Details/ASDealloc2MainObject.m', - ] - #Subspecs + # Subspecs + spec.subspec 'Core' do |core| + core.public_header_files = [ + 'AsyncDisplayKit/*.h', + 'AsyncDisplayKit/Details/**/*.h', + 'AsyncDisplayKit/Layout/*.h', + 'Base/*.h', + 'AsyncDisplayKit/TextKit/ASTextNodeTypes.h', + 'AsyncDisplayKit/TextKit/ASTextKitComponents.h' + ] + + # ASDealloc2MainObject must be compiled with MRR + core.exclude_files = [ + 'AsyncDisplayKit/Private/_AS-objc-internal.h', + 'AsyncDisplayKit/Details/ASDealloc2MainObject.h', + 'AsyncDisplayKit/Details/ASDealloc2MainObject.m', + ] + core.source_files = [ + 'AsyncDisplayKit/**/*.{h,m,mm}', + 'Base/*.{h,m}', + + # Most TextKit components are not public because the C++ content + # in the headers will cause build errors when using + # `use_frameworks!` on 0.39.0 & Swift 2.1. + # See https://github.com/facebook/AsyncDisplayKit/issues/1153 + 'AsyncDisplayKit/TextKit/*.h', + ] + core.dependency 'AsyncDisplayKit/ASDealloc2MainObject' + end spec.subspec 'ASDealloc2MainObject' do |mrr| mrr.requires_arc = false @@ -51,11 +54,11 @@ Pod::Spec.new do |spec| spec.subspec 'PINRemoteImage' do |pin| pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } - pin.dependency 'PINRemoteImage/iOS', '>= 2' - pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject' + pin.dependency 'PINRemoteImage/iOS', '>= 2.1.2' + pin.dependency 'AsyncDisplayKit/Core' end - # Include optional FLAnimatedImage module + # Include optional PINRemoteImage module spec.default_subspec = 'PINRemoteImage' spec.social_media_url = 'https://twitter.com/fbOpenSource' diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index e62a60cf1f..5c12f1cac3 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -105,7 +105,6 @@ 058D0A81195D05F900B7D73C /* ASThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A12195D050800B7D73C /* ASThread.h */; settings = {ATTRIBUTES = (Public, ); }; }; 058D0A82195D060300B7D73C /* ASAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A43195D058D00B7D73C /* ASAssert.h */; settings = {ATTRIBUTES = (Public, ); }; }; 058D0A83195D060300B7D73C /* ASBaseDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A44195D058D00B7D73C /* ASBaseDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A45195D058D00B7D73C /* ASDisplayNodeExtraIvars.h */; settings = {ATTRIBUTES = (Public, ); }; }; 05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */; }; @@ -136,7 +135,7 @@ 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */; }; 254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */; }; 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */; }; - 254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */; }; + 254C6B751BF94DF4003EC431 /* ASTextKitComponents.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */; settings = {ATTRIBUTES = (Public, ); }; }; 254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */; }; 254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754961BEE44CD00737CA5 /* ASTextKitContext.h */; }; @@ -148,7 +147,7 @@ 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; }; 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; }; 254C6B801BF94DF4003EC431 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */; }; - 254C6B821BF94F8A003EC431 /* ASTextKitHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */; }; + 254C6B821BF94F8A003EC431 /* ASTextKitComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */; }; 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; }; 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; }; 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */; }; @@ -179,10 +178,10 @@ 257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; }; 257754B51BEE44CD00737CA5 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754B61BEE44CD00737CA5 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 257754BE1BEE458E00737CA5 /* ASTextKitHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */; }; + 257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */; }; 257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; }; 257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 257754C11BEE458E00737CA5 /* ASTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 257754C11BEE458E00737CA5 /* ASTextKitComponents.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; }; @@ -250,13 +249,61 @@ 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */; }; 509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; + 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; + 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; }; + 68355B301CB5799E001D4E68 /* ASImageNode+AnimatedImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; + 68355B331CB579AD001D4E68 /* ASImageNode+AnimatedImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; + 68355B3A1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */; }; + 68355B3B1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68355B3C1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m */; }; + 68355B3D1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */; }; + 68355B3E1CB57A60001D4E68 /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */; }; + 68355B3F1CB57A64001D4E68 /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */; }; + 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m */; }; + 68355B411CB57A6C001D4E68 /* ASImageContainerProtocolCategories.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */; }; 68B0277A1C1A79CC0041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68B8A4DC1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */; }; + 68B8A4E11CBDB958007E4543 /* ASWeakProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DF1CBDB958007E4543 /* ASWeakProxy.h */; }; + 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DF1CBDB958007E4543 /* ASWeakProxy.h */; }; + 68B8A4E31CBDB958007E4543 /* ASWeakProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 68B8A4E01CBDB958007E4543 /* ASWeakProxy.m */; }; + 68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 68B8A4E01CBDB958007E4543 /* ASWeakProxy.m */; }; 68EE0DBD1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */; }; 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */; }; 68EE0DBF1C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */; }; 68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */; }; + 698548631CA9E025008A345F /* ASEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = 698548611CA9E025008A345F /* ASEnvironment.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 698548641CA9E025008A345F /* ASEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = 698548611CA9E025008A345F /* ASEnvironment.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 698548651CA9E025008A345F /* ASEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 698548621CA9E025008A345F /* ASEnvironment.m */; }; + 698548661CA9E025008A345F /* ASEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 698548621CA9E025008A345F /* ASEnvironment.m */; }; + 698C8B611CAB49FC0052DC3F /* ASLayoutableExtensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 698C8B621CAB49FC0052DC3F /* ASLayoutableExtensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 69CB62AB1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; }; + 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; }; + 69CB62AD1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; }; + 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; }; + 69E1006D1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */; }; + 69E1006E1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */; }; + 69E1006F1CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; }; + 69E100701CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; }; + 69F10C861C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 764D83D51C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 764D83D61C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */; }; + 767E7F8D1C9019130066C000 /* AsyncDisplayKit+Debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */; }; + 7A06A73A1C35F08800FE8DAA /* ASRelativeLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */; }; + 7A06A73B1C35F08800FE8DAA /* ASRelativeLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */; }; + 7AB338671C55B3460055FDE8 /* ASRelativeLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */; }; + 81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; @@ -272,14 +319,6 @@ 9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; }; 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; }; 9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C5FA3511B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5FA34F1B8F6ADF00A62714 /* ASLayoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C5FA3521B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5FA34F1B8F6ADF00A62714 /* ASLayoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C5FA3531B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C5FA3501B8F6ADF00A62714 /* ASLayoutOptions.mm */; }; - 9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C5FA3501B8F6ADF00A62714 /* ASLayoutOptions.mm */; }; - 9C5FA35F1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C5FA35C1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm */; }; - 9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C5FA35C1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm */; }; - 9C65A72A1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */; }; - 9C65A72B1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */; }; 9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C6BB3B31B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; @@ -354,15 +393,15 @@ ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */; }; AEB7B01A1C5962EA00662EF4 /* ASDefaultPlayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */; }; AEB7B01B1C5962EA00662EF4 /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; }; - AEEC47E11C20C2DD00EC1693 /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; }; + AEEC47E11C20C2DD00EC1693 /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; AEEC47E21C20C2DD00EC1693 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.m */; }; B0F8805A1BEAEC7500D17647 /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; B0F8805B1BEAEC7500D17647 /* ASTableNode.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F880591BEAEC7500D17647 /* ASTableNode.m */; }; - B13CA0F71C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; }; - B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; }; - B13CA1001C52004900E031AB /* ASCollectionNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; }; - B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; }; + B13CA0F71C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B13CA1001C52004900E031AB /* ASCollectionNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; B30BF6521C5964B0004FCD53 /* ASLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; }; B30BF6531C5964B0004FCD53 /* ASLayoutManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */; }; B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */; }; @@ -447,7 +486,6 @@ 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 */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; @@ -488,8 +526,12 @@ DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */; }; DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; }; + DE4843DB1C93EAB100A1F33B /* ASDisplayNodeLayoutContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm */; }; + DE4843DC1C93EAC100A1F33B /* ASDisplayNodeLayoutContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h */; }; DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; + DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; DE8BEAC11C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; DE8BEAC31C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; }; @@ -502,6 +544,15 @@ DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; + DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; + E52405B31C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm */; }; + E52405B51C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h */; }; + E55D86321CA8A14000A0C26F /* ASLayoutable.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutable.mm */; }; + E55D86331CA8A14000A0C26F /* ASLayoutable.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutable.mm */; }; + E5711A2B1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E5711A2C1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; }; + E5711A2E1C840C96009619D4 /* ASIndexedNodeContext.m in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.m */; }; + E5711A301C840C96009619D4 /* ASIndexedNodeContext.m in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -558,7 +609,7 @@ 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBasicImageDownloader.mm; sourceTree = ""; }; 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASNetworkImageNode.h; sourceTree = ""; }; 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASNetworkImageNode.mm; sourceTree = ""; }; - 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASTableView.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASTableView.h; sourceTree = ""; }; 055F1A3319ABD3E3004DAFF1 /* ASTableView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTableView.mm; sourceTree = ""; }; 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 = ""; }; @@ -638,7 +689,6 @@ 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeWordKernerTests.mm; sourceTree = ""; }; 058D0A43195D058D00B7D73C /* ASAssert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAssert.h; sourceTree = ""; }; 058D0A44195D058D00B7D73C /* ASBaseDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBaseDefines.h; sourceTree = ""; }; - 058D0A45195D058D00B7D73C /* ASDisplayNodeExtraIvars.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeExtraIvars.h; sourceTree = ""; }; 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDealloc2MainObject.h; path = ../Details/ASDealloc2MainObject.h; sourceTree = ""; }; 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASDealloc2MainObject.m; path = ../Details/ASDealloc2MainObject.m; sourceTree = ""; }; 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASSnapshotTestCase.mm; sourceTree = ""; }; @@ -657,7 +707,7 @@ 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 = ""; }; - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; }; 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = ""; }; 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = ""; }; 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; @@ -684,10 +734,10 @@ 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitTailTruncater.mm; path = TextKit/ASTextKitTailTruncater.mm; sourceTree = ""; }; 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTruncating.h; path = TextKit/ASTextKitTruncating.h; sourceTree = ""; }; 257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASEqualityHashHelpers.h; path = TextKit/ASEqualityHashHelpers.h; sourceTree = ""; }; - 257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitHelpers.mm; path = TextKit/ASTextKitHelpers.mm; sourceTree = ""; }; + 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitComponents.m; path = TextKit/ASTextKitComponents.m; sourceTree = ""; }; 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitCoreTextAdditions.m; path = TextKit/ASTextKitCoreTextAdditions.m; sourceTree = ""; }; 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextNodeWordKerner.h; path = TextKit/ASTextNodeWordKerner.h; sourceTree = ""; }; - 257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitHelpers.h; path = TextKit/ASTextKitHelpers.h; sourceTree = ""; }; + 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitComponents.h; path = TextKit/ASTextKitComponents.h; sourceTree = ""; }; 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitCoreTextAdditions.h; path = TextKit/ASTextKitCoreTextAdditions.h; sourceTree = ""; }; 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextNodeTypes.h; path = TextKit/ASTextNodeTypes.h; sourceTree = ""; }; 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextNodeWordKerner.m; path = TextKit/ASTextNodeWordKerner.m; sourceTree = ""; }; @@ -704,25 +754,45 @@ 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASTableViewTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 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; lineEnding = 0; path = ASDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; }; 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = ""; }; 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; 4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = ""; }; + 68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+AnimatedImage.h"; sourceTree = ""; }; + 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASImageNode+AnimatedImage.mm"; sourceTree = ""; }; + 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPINRemoteImageDownloader.m; sourceTree = ""; }; + 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageContainerProtocolCategories.h; sourceTree = ""; }; + 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASImageContainerProtocolCategories.m; sourceTree = ""; }; + 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPINRemoteImageDownloader.h; sourceTree = ""; }; 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Beta.h"; sourceTree = ""; }; + 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+AnimatedImagePrivate.h"; sourceTree = ""; }; + 68B8A4DF1CBDB958007E4543 /* ASWeakProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakProxy.h; sourceTree = ""; }; + 68B8A4E01CBDB958007E4543 /* ASWeakProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakProxy.m; sourceTree = ""; }; 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMainSerialQueue.h; sourceTree = ""; }; 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMainSerialQueue.mm; sourceTree = ""; }; + 698548611CA9E025008A345F /* ASEnvironment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEnvironment.h; sourceTree = ""; }; + 698548621CA9E025008A345F /* ASEnvironment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASEnvironment.m; sourceTree = ""; }; + 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutableExtensibility.h; path = AsyncDisplayKit/Layout/ASLayoutableExtensibility.h; sourceTree = ""; }; + 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayViewAccessiblity.h; sourceTree = ""; }; + 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayViewAccessiblity.mm; sourceTree = ""; }; + 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEnvironmentInternal.h; sourceTree = ""; }; + 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEnvironmentInternal.mm; sourceTree = ""; }; + 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; + 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit+Debug.h"; sourceTree = ""; }; + 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AsyncDisplayKit+Debug.m"; sourceTree = ""; }; + 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRelativeLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm; sourceTree = ""; }; + 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRelativeLayoutSpec.h; path = AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h; sourceTree = ""; }; + 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = ""; }; + 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = ""; }; + 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = ""; }; 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = ""; }; 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = ""; }; 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; sourceTree = ""; }; 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASAsciiArtBoxCreator.h; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.h; sourceTree = ""; }; 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASAsciiArtBoxCreator.m; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m; sourceTree = ""; }; - 9C5FA34F1B8F6ADF00A62714 /* ASLayoutOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutOptions.h; path = AsyncDisplayKit/Layout/ASLayoutOptions.h; sourceTree = ""; }; - 9C5FA3501B8F6ADF00A62714 /* ASLayoutOptions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASLayoutOptions.mm; path = AsyncDisplayKit/Layout/ASLayoutOptions.mm; sourceTree = ""; }; - 9C5FA35C1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASLayoutOptionsPrivate.mm; path = AsyncDisplayKit/Layout/ASLayoutOptionsPrivate.mm; sourceTree = ""; }; - 9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutOptionsPrivate.h; sourceTree = ""; }; 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStaticLayoutable.h; path = AsyncDisplayKit/Layout/ASStaticLayoutable.h; sourceTree = ""; }; 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackBaselinePositionedLayout.h; sourceTree = ""; }; 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackBaselinePositionedLayout.mm; sourceTree = ""; }; @@ -766,7 +836,7 @@ 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; }; + ACF6ED191B17843500DA7C62 /* ASStaticLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASStaticLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm; sourceTree = ""; }; 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 = ""; }; @@ -774,7 +844,7 @@ 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; }; + ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASStackUnpositionedLayout.mm; sourceTree = ""; }; 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 = ""; }; @@ -824,6 +894,11 @@ DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASCollectionInternal.m; path = Details/ASCollectionInternal.m; sourceTree = ""; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; + E52405B21C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeLayoutContext.mm; sourceTree = ""; }; + E52405B41C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeLayoutContext.h; sourceTree = ""; }; + E55D86311CA8A14000A0C26F /* ASLayoutable.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASLayoutable.mm; path = AsyncDisplayKit/Layout/ASLayoutable.mm; sourceTree = ""; }; + E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexedNodeContext.h; sourceTree = ""; }; + E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexedNodeContext.m; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -973,6 +1048,8 @@ 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */, 058D09DD195D050800B7D73C /* ASImageNode.h */, 058D09DE195D050800B7D73C /* ASImageNode.mm */, + 68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */, + 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */, 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */, 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */, 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */, @@ -993,6 +1070,8 @@ ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */, ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */, 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, + 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */, + 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */, DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */, 058D09E1195D050800B7D73C /* Details */, 058D0A01195D050800B7D73C /* Private */, @@ -1025,6 +1104,7 @@ 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */, 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */, ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */, + 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */, ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */, ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */, ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, @@ -1072,26 +1152,27 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( - 25B171EA1C12242700508A7A /* Data Controller */, - CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */, - CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */, - 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */, - 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */, - 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */, 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */, 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */, 058D09E4195D050800B7D73C /* _ASDisplayView.h */, 058D09E5195D050800B7D73C /* _ASDisplayView.mm */, + 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */, + 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */, 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */, 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */, 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */, 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */, 299DA1A71A828D2900162D41 /* ASBatchContext.h */, 299DA1A81A828D2900162D41 /* ASBatchContext.mm */, + 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */, + 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */, 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */, 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */, + 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */, 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */, 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */, + 698548611CA9E025008A345F /* ASEnvironment.h */, + 698548621CA9E025008A345F /* ASEnvironment.m */, 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */, 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */, 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */, @@ -1099,19 +1180,31 @@ 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */, 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */, 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */, + 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */, + 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m */, + 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */, + 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */, 4640521D1A3F83C40061C0BA /* ASLayoutController.h */, 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */, 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */, 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */, 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */, 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */, + CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */, + CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */, 055F1A3619ABD413004DAFF1 /* ASRangeController.h */, 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */, + 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */, + 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */, + 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */, 296A0A311A951715005ACEAA /* ASScrollDirection.h */, 205F0E111B371BD7007741D0 /* ASScrollDirection.m */, 058D0A12195D050800B7D73C /* ASThread.h */, + 68B8A4DF1CBDB958007E4543 /* ASWeakProxy.h */, + 68B8A4E01CBDB958007E4543 /* ASWeakProxy.m */, 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */, 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */, + 25B171EA1C12242700508A7A /* Data Controller */, 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */, 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */, 058D09F7195D050800B7D73C /* Transactions */, @@ -1139,48 +1232,52 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( - DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */, - DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */, - CC3B20811C3F76D600798563 /* ASPendingStateController.h */, - CC3B20821C3F76D600798563 /* ASPendingStateController.mm */, - CC3B20871C3F7A5400798563 /* ASWeakSet.h */, - CC3B20881C3F7A5400798563 /* ASWeakSet.m */, - AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */, - AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */, - 9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */, - 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */, - 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */, - 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */, 058D0A02195D050800B7D73C /* _AS-objc-internal.h */, 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */, 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */, + AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */, + AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */, 058D0A05195D050800B7D73C /* _ASPendingState.h */, 058D0A06195D050800B7D73C /* _ASPendingState.mm */, 058D0A07195D050800B7D73C /* _ASScopeTimer.h */, - 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */, + DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */, + DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */, + 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */, 044285051BAA63FE00D16268 /* ASBatchFetching.h */, 044285061BAA63FE00D16268 /* ASBatchFetching.m */, + AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */, + AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */, + 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */, 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */, 058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */, - 058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */, DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */, + 058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */, 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */, + E52405B41C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h */, + E52405B21C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm */, + 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */, + 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */, 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */, 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */, - 058D0A10195D050800B7D73C /* ASSentinel.h */, - 058D0A11195D050800B7D73C /* ASSentinel.m */, + 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */, ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */, ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.mm */, ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */, + 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */, + 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */, + CC3B20811C3F76D600798563 /* ASPendingStateController.h */, + CC3B20821C3F76D600798563 /* ASPendingStateController.mm */, + 058D0A10195D050800B7D73C /* ASSentinel.h */, + 058D0A11195D050800B7D73C /* ASSentinel.m */, + 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */, + 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */, ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */, ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */, ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */, ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */, ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */, - 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */, - 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */, - AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */, - AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */, + CC3B20871C3F7A5400798563 /* ASWeakSet.h */, + CC3B20881C3F7A5400798563 /* ASWeakSet.m */, DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, ); @@ -1193,7 +1290,6 @@ 058D0A43195D058D00B7D73C /* ASAssert.h */, 0516FA3A1A15563400B4EBED /* ASAvailability.h */, 058D0A44195D058D00B7D73C /* ASBaseDefines.h */, - 058D0A45195D058D00B7D73C /* ASDisplayNodeExtraIvars.h */, 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */, 0516FA3B1A15563400B4EBED /* ASLog.h */, ); @@ -1205,11 +1301,11 @@ children = ( B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */, B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */, - 257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */, + 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */, + 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */, 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */, 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */, 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */, - 257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */, 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */, 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */, 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */, @@ -1248,6 +1344,8 @@ 4640521A1A3F83C40061C0BA /* ASDataController.mm */, AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */, AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */, + E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */, + E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.m */, ); name = "Data Controller"; sourceTree = ""; @@ -1263,13 +1361,13 @@ ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */, ACF6ED071B17843500DA7C62 /* ASDimension.h */, ACF6ED081B17843500DA7C62 /* ASDimension.mm */, - AC47D9431B3BB41900AAEE9D /* ASRelativeSize.h */, - AC47D9441B3BB41900AAEE9D /* ASRelativeSize.mm */, ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */, ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */, ACF6ED0B1B17843500DA7C62 /* ASLayout.h */, ACF6ED0C1B17843500DA7C62 /* ASLayout.mm */, ACF6ED111B17843500DA7C62 /* ASLayoutable.h */, + E55D86311CA8A14000A0C26F /* ASLayoutable.mm */, + 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */, 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */, ACF6ED0D1B17843500DA7C62 /* ASLayoutSpec.h */, ACF6ED0E1B17843500DA7C62 /* ASLayoutSpec.mm */, @@ -1277,6 +1375,10 @@ ACF6ED131B17843500DA7C62 /* ASOverlayLayoutSpec.mm */, ACF6ED141B17843500DA7C62 /* ASRatioLayoutSpec.h */, ACF6ED151B17843500DA7C62 /* ASRatioLayoutSpec.mm */, + 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */, + 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */, + AC47D9431B3BB41900AAEE9D /* ASRelativeSize.h */, + AC47D9441B3BB41900AAEE9D /* ASRelativeSize.mm */, 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */, AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */, ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */, @@ -1284,9 +1386,6 @@ 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */, ACF6ED181B17843500DA7C62 /* ASStaticLayoutSpec.h */, ACF6ED191B17843500DA7C62 /* ASStaticLayoutSpec.mm */, - 9C5FA34F1B8F6ADF00A62714 /* ASLayoutOptions.h */, - 9C5FA3501B8F6ADF00A62714 /* ASLayoutOptions.mm */, - 9C5FA35C1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm */, ); name = Layout; path = ..; @@ -1325,8 +1424,12 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 698C8B611CAB49FC0052DC3F /* ASLayoutableExtensibility.h in Headers */, + 698548631CA9E025008A345F /* ASEnvironment.h in Headers */, + E5711A2B1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */, 257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */, A373200F1C571B730011FC94 /* ASTextNode+Beta.h in Headers */, + 69F10C861C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */, 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */, AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */, 058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */, @@ -1336,6 +1439,8 @@ 058D0A6B195D05EC00B7D73C /* _ASAsyncTransactionContainer.h in Headers */, 058D0A6D195D05EC00B7D73C /* _ASAsyncTransactionGroup.h in Headers */, 058D0A72195D05F800B7D73C /* _ASCoreAnimationExtras.h in Headers */, + 7A06A73B1C35F08800FE8DAA /* ASRelativeLayoutSpec.h in Headers */, + 68B8A4DC1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h in Headers */, 058D0A53195D05DC00B7D73C /* _ASDisplayLayer.h in Headers */, 058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */, B13CA0F71C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */, @@ -1373,13 +1478,15 @@ ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */, 058D0A78195D05F900B7D73C /* ASDisplayNode+DebugTiming.h in Headers */, DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */, + 68B8A4E11CBDB958007E4543 /* ASWeakProxy.h in Headers */, DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */, 058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */, 058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */, - 058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */, AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, 058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */, + 68355B3B1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h in Headers */, 68B0277A1C1A79CC0041016B /* ASDisplayNode+Beta.h in Headers */, + 767E7F8D1C9019130066C000 /* AsyncDisplayKit+Debug.h in Headers */, 257754B11BEE44CD00737CA5 /* ASTextKitShadower.h in Headers */, 058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */, 0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */, @@ -1398,14 +1505,13 @@ ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */, ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */, DB55C2661C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, + 68355B3D1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h in Headers */, ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */, 251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */, ACF6ED2A1B17843500DA7C62 /* ASLayoutable.h in Headers */, 9CDC18CC1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */, B0F8805A1BEAEC7500D17647 /* ASTableNode.h in Headers */, 464052241A3F83C40061C0BA /* ASLayoutController.h in Headers */, - 9C5FA3511B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */, - 9C65A72A1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */, 292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */, 257754B61BEE44CD00737CA5 /* ASEqualityHashHelpers.h in Headers */, ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */, @@ -1420,6 +1526,7 @@ 055B9FA81A1C154B00035D6D /* ASNetworkImageNode.h in Headers */, ACF6ED2B1B17843500DA7C62 /* ASOverlayLayoutSpec.h in Headers */, 055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */, + E52405B51C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h in Headers */, ACF6ED2D1B17843500DA7C62 /* ASRatioLayoutSpec.h in Headers */, AC47D9451B3BB41900AAEE9D /* ASRelativeSize.h in Headers */, 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */, @@ -1428,6 +1535,8 @@ 9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */, 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */, + 69E1006D1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */, + 68355B301CB5799E001D4E68 /* ASImageNode+AnimatedImage.h in Headers */, AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */, CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */, @@ -1441,10 +1550,12 @@ ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */, 055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */, 251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */, - 257754C11BEE458E00737CA5 /* ASTextKitHelpers.h in Headers */, + 257754C11BEE458E00737CA5 /* ASTextKitComponents.h in Headers */, B30BF6521C5964B0004FCD53 /* ASLayoutManager.h in Headers */, 0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */, + 81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */, CC3B20831C3F76D600798563 /* ASPendingStateController.h in Headers */, + 69CB62AB1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */, 058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */, 058D0A81195D05F900B7D73C /* ASThread.h in Headers */, ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */, @@ -1462,9 +1573,14 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 698C8B621CAB49FC0052DC3F /* ASLayoutableExtensibility.h in Headers */, + 698548641CA9E025008A345F /* ASEnvironment.h in Headers */, AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */, B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */, + 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */, B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */, + 68355B411CB57A6C001D4E68 /* ASImageContainerProtocolCategories.h in Headers */, + 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */, B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */, B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, @@ -1481,10 +1597,14 @@ B35062571B010F070018CF92 /* ASAssert.h in Headers */, 254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */, B35062581B010F070018CF92 /* ASAvailability.h in Headers */, + DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */, 254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */, 254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */, + 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */, + 68355B3F1CB57A64001D4E68 /* ASPINRemoteImageDownloader.h in Headers */, 254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */, 34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */, + 68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */, B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */, B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */, B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */, @@ -1512,7 +1632,6 @@ B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */, B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */, B35061FB1B010EFD0018CF92 /* ASDisplayNode.h in Headers */, - B350625A1B010F070018CF92 /* ASDisplayNodeExtraIvars.h in Headers */, B35061FE1B010EFD0018CF92 /* ASDisplayNodeExtras.h in Headers */, B35062521B010EFD0018CF92 /* ASDisplayNodeInternal.h in Headers */, B35062001B010EFD0018CF92 /* ASEditableTextNode.h in Headers */, @@ -1523,19 +1642,20 @@ AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */, 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */, + 7AB338671C55B3460055FDE8 /* ASRelativeLayoutSpec.h in Headers */, B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */, B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */, 430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */, 34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */, 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */, 34EFC7671B701CD900AD841F /* ASLayout.h in Headers */, + 68355B331CB579AD001D4E68 /* ASImageNode+AnimatedImage.h in Headers */, DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */, 34EFC7691B701CE100AD841F /* ASLayoutable.h in Headers */, 9CDC18CD1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */, + 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */, B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */, - 9C5FA3521B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */, - 9C65A72B1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */, B35062211B010EFD0018CF92 /* ASLayoutRangeType.h in Headers */, 34EFC76A1B701CE600AD841F /* ASLayoutSpec.h in Headers */, 34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */, @@ -1557,7 +1677,7 @@ DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */, B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */, - 254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */, + 254C6B751BF94DF4003EC431 /* ASTextKitComponents.h in Headers */, B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, 25E327571C16819500A2170C /* ASPagerNode.h in Headers */, B35062551B010EFD0018CF92 /* ASSentinel.h in Headers */, @@ -1565,14 +1685,18 @@ 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */, DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, + 764D83D51C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h in Headers */, + E5711A2C1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */, 254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */, CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, + DE4843DC1C93EAC100A1F33B /* ASDisplayNodeLayoutContext.h in Headers */, 254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */, 34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */, 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */, 92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */, 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */, 34EFC7751B701D2400AD841F /* ASStackPositionedLayout.h in Headers */, + 69E1006E1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */, 34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */, 9C6BB3B31B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */, 34EFC7731B701D0700AD841F /* ASStaticLayoutSpec.h in Headers */, @@ -1637,6 +1761,7 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, + B130AB1AC0A1E5162E211C19 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1673,7 +1798,8 @@ 058D09A4195D04C000B7D73C /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0710; + CLASSPREFIX = AS; + LastUpgradeCheck = 0720; ORGANIZATIONNAME = Facebook; TargetAttributes = { 057D02BE1AC0A66700C7AC3C = { @@ -1766,6 +1892,21 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; + B130AB1AC0A1E5162E211C19 /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1783,14 +1924,19 @@ buildActionMask = 2147483647; files = ( 058D0A22195D050800B7D73C /* _ASAsyncTransaction.mm in Sources */, + E55D86321CA8A14000A0C26F /* ASLayoutable.mm in Sources */, 058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */, 058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */, + 68355B3A1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m in Sources */, DBDB83961C6E879900D0098C /* ASPagerFlowLayout.m in Sources */, 058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */, 257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */, + 68B8A4E31CBDB958007E4543 /* ASWeakProxy.m in Sources */, + 69E1006F1CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */, AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */, 257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */, 058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */, + 68355B3C1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m in Sources */, 68EE0DBF1C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */, 058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */, 9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */, @@ -1814,6 +1960,7 @@ ACF6ED211B17843500DA7C62 /* ASDimension.mm in Sources */, 058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */, 058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */, + 764D83D61C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m in Sources */, 058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */, 25E327581C16819500A2170C /* ASPagerNode.m in Sources */, 058D0A14195D050800B7D73C /* ASDisplayNode.mm in Sources */, @@ -1830,10 +1977,9 @@ 430E7C911B4C23F100697A4C /* ASIndexPath.m in Sources */, ACF6ED231B17843500DA7C62 /* ASInsetLayoutSpec.mm in Sources */, ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.mm in Sources */, + 698548651CA9E025008A345F /* ASEnvironment.m in Sources */, ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */, DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */, - 9C5FA3531B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */, - 9C5FA35F1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */, 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */, ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */, 257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */, @@ -1845,7 +1991,10 @@ CC3B20851C3F76D600798563 /* ASPendingStateController.mm in Sources */, ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */, 0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */, + 7A06A73A1C35F08800FE8DAA /* ASRelativeLayoutSpec.mm in Sources */, 257754921BED28F300737CA5 /* ASEqualityHashHelpers.mm in Sources */, + E52405B31C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm in Sources */, + 69CB62AD1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, 257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */, 055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */, 044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */, @@ -1855,17 +2004,20 @@ 205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */, 9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, D785F6631A74327E00291744 /* ASScrollNode.m in Sources */, + E5711A2E1C840C96009619D4 /* ASIndexedNodeContext.m in Sources */, 058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */, 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */, ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */, - 257754BE1BEE458E00737CA5 /* ASTextKitHelpers.mm in Sources */, + 257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */, 257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */, ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */, ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */, 257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */, + 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */, ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */, AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */, + 68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */, 055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */, 058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */, 257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */, @@ -1905,6 +2057,7 @@ 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */, ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */, ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */, + 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */, 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */, 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, @@ -1924,8 +2077,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DE4843DB1C93EAB100A1F33B /* ASDisplayNodeLayoutContext.mm in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */, 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, + 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */, 9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */, 9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */, B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.mm in Sources */, @@ -1934,10 +2089,12 @@ B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */, B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */, 68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */, + 698548661CA9E025008A345F /* ASEnvironment.m in Sources */, 2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */, B35062101B010EFD0018CF92 /* _ASDisplayLayer.mm in Sources */, 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.m in Sources */, B35062121B010EFD0018CF92 /* _ASDisplayView.mm in Sources */, + DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */, B350624C1B010EFD0018CF92 /* _ASPendingState.mm in Sources */, 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, @@ -1950,17 +2107,25 @@ AC47D9421B3B891B00AAEE9D /* ASCellNode.m in Sources */, 34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */, 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */, + E55D86331CA8A14000A0C26F /* ASLayoutable.mm in Sources */, + 68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */, + 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, B350621A1B010EFD0018CF92 /* ASDealloc2MainObject.m in Sources */, + 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */, 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */, B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */, 25E327591C16819500A2170C /* ASPagerNode.m in Sources */, + 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */, B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */, DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */, + 69E100701CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */, 254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */, + 68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */, + E5711A301C840C96009619D4 /* ASIndexedNodeContext.m in Sources */, B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */, B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */, B35061FF1B010EFD0018CF92 /* ASDisplayNodeExtras.mm in Sources */, @@ -1970,14 +2135,13 @@ B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */, B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */, B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */, + 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */, B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */, - 254C6B821BF94F8A003EC431 /* ASTextKitHelpers.mm in Sources */, + 254C6B821BF94F8A003EC431 /* ASTextKitComponents.m in Sources */, 430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */, 34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */, 34EFC75E1B701BF000AD841F /* ASInternalHelpers.mm in Sources */, 34EFC7681B701CDE00AD841F /* ASLayout.mm in Sources */, - 9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */, - 9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */, DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */, 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */, 34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */, @@ -2000,12 +2164,15 @@ 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */, 34EFC7761B701D2A00AD841F /* ASStackPositionedLayout.mm in Sources */, + 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */, 34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */, + DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */, 34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */, DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */, B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */, B350620E1B010EFD0018CF92 /* ASTextNode.mm in Sources */, + 68355B3E1CB57A60001D4E68 /* ASPINRemoteImageDownloader.m in Sources */, C78F7E2A1BF7808300CDEAFC /* ASTableNode.m in Sources */, 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */, 254C6B8D1BF94F8A003EC431 /* ASEqualityHashHelpers.mm in Sources */, @@ -2066,6 +2233,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -2083,6 +2251,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; @@ -2091,7 +2260,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_CODE_COVERAGE = NO; CLANG_ENABLE_MODULES = YES; @@ -2132,7 +2301,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_CODE_COVERAGE = NO; CLANG_ENABLE_MODULES = YES; @@ -2211,7 +2380,6 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_CODE_COVERAGE = YES; FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); @@ -2228,6 +2396,7 @@ GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; INFOPLIST_FILE = "AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 7.1; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost"; WRAPPER_EXTENSION = xctest; @@ -2241,7 +2410,6 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_CODE_COVERAGE = YES; FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); @@ -2257,6 +2425,7 @@ GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; INFOPLIST_FILE = "AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 7.1; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost"; WRAPPER_EXTENSION = xctest; @@ -2289,6 +2458,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = AsyncDisplayKit/module.modulemap; MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AsyncDisplayKit; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2320,6 +2490,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = AsyncDisplayKit/module.modulemap; MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AsyncDisplayKit; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2328,6 +2499,140 @@ }; name = Release; }; + DB1020801CBCA2AD00FA6FE1 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_CODE_COVERAGE = NO; + 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__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_GENERATE_TEST_COVERAGE_FILES = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + 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 = 7.1; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + DB1020811CBCA2AD00FA6FE1 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_CODE_COVERAGE = YES; + DSTROOT = /tmp/AsyncDisplayKit.dst; + GCC_INPUT_FILETYPE = automatic; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + OTHER_CFLAGS = "-Wall"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PUBLIC_HEADERS_FOLDER_PATH = "include/$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Profile; + }; + DB1020821CBCA2AD00FA6FE1 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_CODE_COVERAGE = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "FB_REFERENCE_IMAGE_DIR=\"\\\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\\\"\"", + ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + INFOPLIST_FILE = "AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost"; + WRAPPER_EXTENSION = xctest; + }; + name = Profile; + }; + DB1020831CBCA2AD00FA6FE1 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_WARN_UNREACHABLE_CODE = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; + INFOPLIST_FILE = AsyncDisplayKitTestHost/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + DB1020841CBCA2AD00FA6FE1 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_CODE_COVERAGE = YES; + 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_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; + INFOPLIST_FILE = "$(SRCROOT)/AsyncDisplayKit-iOS/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = AsyncDisplayKit/module.modulemap; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = AsyncDisplayKit; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Profile; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -2336,6 +2641,7 @@ buildConfigurations = ( 057D02DF1AC0A66800C7AC3C /* Debug */, 057D02E01AC0A66800C7AC3C /* Release */, + DB1020831CBCA2AD00FA6FE1 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -2345,6 +2651,7 @@ buildConfigurations = ( 058D09CD195D04C000B7D73C /* Debug */, 058D09CE195D04C000B7D73C /* Release */, + DB1020801CBCA2AD00FA6FE1 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -2354,6 +2661,7 @@ buildConfigurations = ( 058D09D0195D04C000B7D73C /* Debug */, 058D09D1195D04C000B7D73C /* Release */, + DB1020811CBCA2AD00FA6FE1 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -2363,6 +2671,7 @@ buildConfigurations = ( 058D09D3195D04C000B7D73C /* Debug */, 058D09D4195D04C000B7D73C /* Release */, + DB1020821CBCA2AD00FA6FE1 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -2372,6 +2681,7 @@ buildConfigurations = ( B35061EE1B010EDF0018CF92 /* Debug */, B35061EF1B010EDF0018CF92 /* Release */, + DB1020841CBCA2AD00FA6FE1 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/AsyncDisplayKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/AsyncDisplayKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 235ea95fac..919434a625 100644 --- a/AsyncDisplayKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/AsyncDisplayKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme index 50760cce7f..ae3c3d3a9d 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme @@ -1,6 +1,6 @@ + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + diff --git a/AsyncDisplayKit/ASButtonNode.h b/AsyncDisplayKit/ASButtonNode.h index e89082d9bc..8de59c6df0 100644 --- a/AsyncDisplayKit/ASButtonNode.h +++ b/AsyncDisplayKit/ASButtonNode.h @@ -28,19 +28,17 @@ @property (nonatomic, assign) BOOL laysOutHorizontally; /** Horizontally align content (text or image). - Defaults to ASAlignmentMiddle. + Defaults to ASHorizontalAlignmentMiddle. */ @property (nonatomic, assign) ASHorizontalAlignment contentHorizontalAlignment; /** Vertically align content (text or image). - Defaults to ASAlignmentCenter. + Defaults to ASVerticalAlignmentCenter. */ @property (nonatomic, assign) ASVerticalAlignment contentVerticalAlignment; /** - * @discussion insets the title and the image node - * - * @param contentEdgeInsets The insets used around the title and image node + * @discussion The insets used around the title and image node */ @property (nonatomic, assign) UIEdgeInsets contentEdgeInsets; diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 03c3ab7b0c..5d875a3318 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -21,16 +21,19 @@ NSAttributedString *_normalAttributedTitle; NSAttributedString *_highlightedAttributedTitle; NSAttributedString *_selectedAttributedTitle; + NSAttributedString *_selectedHighlightedAttributedTitle; NSAttributedString *_disabledAttributedTitle; UIImage *_normalImage; UIImage *_highlightedImage; UIImage *_selectedImage; + UIImage *_selectedHighlightedImage; UIImage *_disabledImage; UIImage *_normalBackgroundImage; UIImage *_highlightedBackgroundImage; UIImage *_selectedBackgroundImage; + UIImage *_selectedHighlightedBackgroundImage; UIImage *_disabledBackgroundImage; } @@ -54,9 +57,10 @@ _contentSpacing = 8.0; _laysOutHorizontally = YES; - _contentHorizontalAlignment = ASAlignmentMiddle; - _contentVerticalAlignment = ASAlignmentCenter; + _contentHorizontalAlignment = ASHorizontalAlignmentMiddle; + _contentVerticalAlignment = ASVerticalAlignmentCenter; _contentEdgeInsets = UIEdgeInsetsZero; + self.accessibilityTraits = UIAccessibilityTraitButton; } return self; } @@ -66,6 +70,7 @@ if (!_titleNode) { _titleNode = [[ASTextNode alloc] init]; [_titleNode setLayerBacked:YES]; + [_titleNode setFlexShrink:YES]; } return _titleNode; } @@ -75,7 +80,6 @@ if (!_imageNode) { _imageNode = [[ASImageNode alloc] init]; [_imageNode setLayerBacked:YES]; - [_titleNode setFlexShrink:YES]; } return _imageNode; } @@ -99,6 +103,11 @@ - (void)setEnabled:(BOOL)enabled { [super setEnabled:enabled]; + if (enabled) { + self.accessibilityTraits = UIAccessibilityTraitButton; + } else { + self.accessibilityTraits = UIAccessibilityTraitButton | UIAccessibilityTraitNotEnabled; + } [self updateButtonContent]; } @@ -132,10 +141,12 @@ - (void)updateImage { ASDN::MutexLocker l(_propertyLock); - + UIImage *newImage; if (self.enabled == NO && _disabledImage) { newImage = _disabledImage; + } else if (self.highlighted && self.selected && _selectedHighlightedImage) { + newImage = _selectedHighlightedImage; } else if (self.highlighted && _highlightedImage) { newImage = _highlightedImage; } else if (self.selected && _selectedImage) { @@ -156,6 +167,8 @@ NSAttributedString *newTitle; if (self.enabled == NO && _disabledAttributedTitle) { newTitle = _disabledAttributedTitle; + } else if (self.highlighted && self.selected && _selectedHighlightedAttributedTitle) { + newTitle = _selectedHighlightedAttributedTitle; } else if (self.highlighted && _highlightedAttributedTitle) { newTitle = _highlightedAttributedTitle; } else if (self.selected && _selectedAttributedTitle) { @@ -163,9 +176,10 @@ } else { newTitle = _normalAttributedTitle; } - + if ((_titleNode != nil || newTitle.length > 0) && newTitle != self.titleNode.attributedString) { _titleNode.attributedString = newTitle; + self.accessibilityLabel = _titleNode.accessibilityLabel; [self setNeedsLayout]; } } @@ -177,6 +191,8 @@ UIImage *newImage; if (self.enabled == NO && _disabledBackgroundImage) { newImage = _disabledBackgroundImage; + } else if (self.highlighted && self.selected && _selectedHighlightedBackgroundImage) { + newImage = _selectedHighlightedBackgroundImage; } else if (self.highlighted && _highlightedBackgroundImage) { newImage = _highlightedBackgroundImage; } else if (self.selected && _selectedBackgroundImage) { @@ -264,8 +280,8 @@ - (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(ASControlState)state { NSDictionary *attributes = @{ - NSFontAttributeName: font ? font :[UIFont systemFontOfSize:[UIFont buttonFontSize]], - NSForegroundColorAttributeName : color ? color : [UIColor blackColor] + NSFontAttributeName: font ? : [UIFont systemFontOfSize:[UIFont buttonFontSize]], + NSForegroundColorAttributeName : color ? : [UIColor blackColor] }; NSAttributedString *string = [[NSAttributedString alloc] initWithString:title @@ -286,6 +302,9 @@ case ASControlStateSelected: return _selectedAttributedTitle; + + case ASControlStateSelected | ASControlStateHighlighted: + return _selectedHighlightedAttributedTitle; case ASControlStateDisabled: return _disabledAttributedTitle; @@ -310,6 +329,10 @@ case ASControlStateSelected: _selectedAttributedTitle = [title copy]; break; + + case ASControlStateSelected | ASControlStateHighlighted: + _selectedHighlightedAttributedTitle = [title copy]; + break; case ASControlStateDisabled: _disabledAttributedTitle = [title copy]; @@ -318,6 +341,7 @@ default: break; } + [self updateTitle]; } @@ -334,6 +358,9 @@ case ASControlStateSelected: return _selectedImage; + case ASControlStateSelected | ASControlStateHighlighted: + return _selectedHighlightedImage; + case ASControlStateDisabled: return _disabledImage; @@ -357,7 +384,11 @@ case ASControlStateSelected: _selectedImage = image; break; - + + case ASControlStateSelected | ASControlStateHighlighted: + _selectedHighlightedImage = image; + break; + case ASControlStateDisabled: _disabledImage = image; break; @@ -368,6 +399,30 @@ [self updateImage]; } +- (UIImage *)backgroundImageForState:(ASControlState)state +{ + ASDN::MutexLocker l(_propertyLock); + switch (state) { + case ASControlStateNormal: + return _normalBackgroundImage; + + case ASControlStateHighlighted: + return _highlightedBackgroundImage; + + case ASControlStateSelected: + return _selectedBackgroundImage; + + case ASControlStateSelected | ASControlStateHighlighted: + return _selectedHighlightedBackgroundImage; + + case ASControlStateDisabled: + return _disabledBackgroundImage; + + default: + return _normalBackgroundImage; + } +} + - (void)setBackgroundImage:(UIImage *)image forState:(ASControlState)state { ASDN::MutexLocker l(_propertyLock); @@ -383,6 +438,10 @@ case ASControlStateSelected: _selectedBackgroundImage = image; break; + + case ASControlStateSelected | ASControlStateHighlighted: + _selectedHighlightedBackgroundImage = image; + break; case ASControlStateDisabled: _disabledBackgroundImage = image; @@ -394,28 +453,6 @@ [self updateBackgroundImage]; } -- (UIImage *)backgroundImageForState:(ASControlState)state -{ - ASDN::MutexLocker l(_propertyLock); - switch (state) { - case ASControlStateNormal: - return _normalBackgroundImage; - - case ASControlStateHighlighted: - return _highlightedBackgroundImage; - - case ASControlStateSelected: - return _selectedBackgroundImage; - - case ASControlStateDisabled: - return _disabledBackgroundImage; - - default: - return _normalBackgroundImage; - } - -} - - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { UIEdgeInsets contentEdgeInsets; diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h index 8dba99cded..5241456b9f 100644 --- a/AsyncDisplayKit/ASCellNode+Internal.h +++ b/AsyncDisplayKit/ASCellNode+Internal.h @@ -32,4 +32,9 @@ */ @property (nonatomic, weak) id layoutDelegate; +/* + * Back-pointer to the containing scrollView instance, set only for visible cells. Used for Cell Visibility Event callbacks. + */ +@property (nonatomic, weak) UIScrollView *scrollView; + @end diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index fccc62997b..e38376bee4 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -14,6 +14,24 @@ NS_ASSUME_NONNULL_BEGIN typedef NSUInteger ASCellNodeAnimation; +typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { + /** + * Indicates a cell has just became visible + */ + ASCellNodeVisibilityEventVisible, + /** + * Its position (determined by scrollView.contentOffset) has changed while at least 1px remains visible. + * It is possible that 100% of the cell is visible both before and after and only its position has changed, + * or that the position change has resulted in more or less of the cell being visible. + * Use CGRectIntersect between cellFrame and scrollView.bounds to get this rectangle + */ + ASCellNodeVisibilityEventVisibleRectChanged, + /** + * Indicates a cell is no longer visible + */ + ASCellNodeVisibilityEventInvisible, +}; + /** * Generic cell node. Subclass this instead of `ASDisplayNode` to use with `ASTableView` and `ASCollectionView`. */ @@ -33,7 +51,7 @@ typedef NSUInteger ASCellNodeAnimation; * * With this property set to YES, the main thread will be blocked until display is complete for * the cell. This is more similar to UIKit, and in fact makes AsyncDisplayKit scrolling visually - * indistinguishible from UIKit's, except being faster. + * indistinguishable from UIKit's, except being faster. * * Using this option does not eliminate all of the performance advantages of AsyncDisplayKit. * Normally, a cell has been preloading and is almost done when it reaches the screen, so the @@ -48,12 +66,12 @@ typedef NSUInteger ASCellNodeAnimation; //@property (atomic, retain) UIColor *backgroundColor; @property (nonatomic) UITableViewCellSelectionStyle selectionStyle; -/* +/** * A Boolean value that indicates whether the node is selected. */ @property (nonatomic, assign) BOOL selected; -/* +/** * A Boolean value that indicates whether the node is highlighted. */ @property (nonatomic, assign) BOOL highlighted; @@ -85,12 +103,12 @@ typedef NSUInteger ASCellNodeAnimation; * @param didLoadBlock The block that will be called after the view controller's view is loaded. * * @return An ASCellNode created using the root view of the view controller provided by the viewControllerBlock. - * The view controller's root view is resized to match the calcuated size produced during layout. + * The view controller's root view is resized to match the calculated size produced during layout. * */ - (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock; -- (void)visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; +- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; @end @@ -100,11 +118,26 @@ typedef NSUInteger ASCellNodeAnimation; */ @interface ASTextCellNode : ASCellNode +/** + * Initializes a text cell with given text attributes and text insets + */ +- (instancetype)initWithAttributes:(NSDictionary *)textAttributes insets:(UIEdgeInsets)textInsets; + /** * Text to display. */ @property (nonatomic, copy) NSString *text; +/** + * A dictionary containing key-value pairs for text attributes. You can specify the font, text color, text shadow color, and text shadow offset using the keys listed in NSString UIKit Additions Reference. + */ +@property (nonatomic, copy) NSDictionary *textAttributes; + +/** + * The text inset or outset for each edge. The default value is 15.0 horizontal and 11.0 vertical padding. + */ +@property (nonatomic, assign) UIEdgeInsets textInsets; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 5083edf12c..85f9a08f0b 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -9,12 +9,15 @@ #import "ASCellNode+Internal.h" #import "ASInternalHelpers.h" +#import "ASEqualityHelpers.h" #import #import +#import #import #import #import +#import #pragma mark - #pragma mark ASCellNode @@ -24,6 +27,7 @@ ASDisplayNodeViewControllerBlock _viewControllerBlock; ASDisplayNodeDidLoadBlock _viewControllerDidLoadBlock; ASDisplayNode *_viewControllerNode; + UIViewController *_viewController; } @end @@ -60,15 +64,16 @@ if (_viewControllerBlock != nil) { - UIViewController *viewController = _viewControllerBlock(); + _viewController = _viewControllerBlock(); _viewControllerBlock = nil; - if ([viewController isKindOfClass:[ASViewController class]]) { - ASViewController *asViewController = (ASViewController *)viewController; + if ([_viewController isKindOfClass:[ASViewController class]]) { + ASViewController *asViewController = (ASViewController *)_viewController; _viewControllerNode = asViewController.node; + [_viewController view]; } else { _viewControllerNode = [[ASDisplayNode alloc] initWithViewBlock:^{ - return viewController.view; + return _viewController.view; }]; } [self addSubnode:_viewControllerNode]; @@ -119,15 +124,56 @@ { CGSize oldSize = self.calculatedSize; [super setNeedsLayout]; + [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; +} +- (void)transitionLayoutWithAnimation:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(void(^)())completion +{ + CGSize oldSize = self.calculatedSize; + [super transitionLayoutWithAnimation:animated + shouldMeasureAsync:shouldMeasureAsync + measurementCompletion:^{ + [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; + if (completion) { + completion(); + } + } + ]; +} + +- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize + animated:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(void(^)())completion +{ + CGSize oldSize = self.calculatedSize; + [super transitionLayoutWithSizeRange:constrainedSize + animated:animated + shouldMeasureAsync:shouldMeasureAsync + measurementCompletion:^{ + [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; + if (completion) { + completion(); + } + } + ]; +} + +- (void)didRelayoutFromOldSize:(CGSize)oldSize toNewSize:(CGSize)newSize +{ if (_layoutDelegate != nil && self.isNodeLoaded) { ASPerformBlockOnMainThread(^{ - BOOL sizeChanged = !CGSizeEqualToSize(oldSize, self.calculatedSize); + BOOL sizeChanged = !CGSizeEqualToSize(oldSize, newSize); [_layoutDelegate nodeDidRelayout:self sizeChanged:sizeChanged]; }); } } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" + - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { ASDisplayNodeAssertMainThread(); @@ -156,9 +202,25 @@ [(_ASDisplayView *)self.view __forwardTouchesCancelled:touches withEvent:event]; } -- (void)visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame +#pragma clang diagnostic pop + +- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame { - // To be overriden by subclasses + // To be overriden by subclasses +} + +- (void)visibilityDidChange:(BOOL)isVisible +{ + [super visibilityDidChange:isVisible]; + + CGRect cellFrame = CGRectZero; + if (_scrollView) { + // It is not safe to message nil with a structure return value, so ensure our _scrollView has not died. + cellFrame = [self.view convertRect:self.bounds toView:_scrollView]; + } + [self cellNodeVisibilityEvent:isVisible ? ASCellNodeVisibilityEventVisible : ASCellNodeVisibilityEventInvisible + inScrollView:_scrollView + withCellFrame:cellFrame]; } @end @@ -168,46 +230,83 @@ #pragma mark ASTextCellNode @interface ASTextCellNode () -{ - NSString *_text; - ASTextNode *_textNode; -} + +@property (nonatomic, strong) ASTextNode *textNode; @end @implementation ASTextCellNode -static const CGFloat kFontSize = 18.0f; +static const CGFloat kASTextCellNodeDefaultFontSize = 18.0f; +static const CGFloat kASTextCellNodeDefaultHorizontalPadding = 15.0f; +static const CGFloat kASTextCellNodeDefaultVerticalPadding = 11.0f; - (instancetype)init { - if (!(self = [super init])) - return nil; - - _text = @""; - _textNode = [[ASTextNode alloc] init]; - [self addSubnode:_textNode]; + return [self initWithAttributes:[self defaultTextAttributes] insets:[self defaultTextInsets]]; +} +- (instancetype)initWithAttributes:(NSDictionary *)textAttributes insets:(UIEdgeInsets)textInsets +{ + self = [super init]; + if (self) { + _textInsets = textInsets; + _textAttributes = [textAttributes copy]; + _textNode = [[ASTextNode alloc] init]; + [self addSubnode:_textNode]; + } return self; } - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { - static const CGFloat kHorizontalPadding = 15.0f; - static const CGFloat kVerticalPadding = 11.0f; - UIEdgeInsets insets = UIEdgeInsetsMake(kVerticalPadding, kHorizontalPadding, kVerticalPadding, kHorizontalPadding); - return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_textNode]; + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:self.textInsets child:self.textNode]; +} + +- (NSDictionary *)defaultTextAttributes +{ + return @{NSFontAttributeName : [UIFont systemFontOfSize:kASTextCellNodeDefaultFontSize]}; +} + +- (UIEdgeInsets)defaultTextInsets +{ + return UIEdgeInsetsMake(kASTextCellNodeDefaultVerticalPadding, kASTextCellNodeDefaultHorizontalPadding, kASTextCellNodeDefaultVerticalPadding, kASTextCellNodeDefaultHorizontalPadding); +} + +- (void)setTextAttributes:(NSDictionary *)textAttributes +{ + ASDisplayNodeAssertNotNil(textAttributes, @"Invalid text attributes"); + + _textAttributes = [textAttributes copy]; + + [self updateAttributedString]; +} + +- (void)setTextInsets:(UIEdgeInsets)textInsets +{ + _textInsets = textInsets; + + [self updateAttributedString]; } - (void)setText:(NSString *)text { - if (_text == text) - return; + if (ASObjectIsEqual(_text, text)) return; _text = [text copy]; - _textNode.attributedString = [[NSAttributedString alloc] initWithString:_text - attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kFontSize]}]; + + [self updateAttributedString]; +} + +- (void)updateAttributedString +{ + if (_text == nil) { + _textNode.attributedString = nil; + return; + } + + _textNode.attributedString = [[NSAttributedString alloc] initWithString:self.text attributes:self.textAttributes]; [self setNeedsLayout]; } diff --git a/AsyncDisplayKit/ASCollectionNode+Beta.h b/AsyncDisplayKit/ASCollectionNode+Beta.h index e05e740ba2..a2073d4473 100644 --- a/AsyncDisplayKit/ASCollectionNode+Beta.h +++ b/AsyncDisplayKit/ASCollectionNode+Beta.h @@ -14,9 +14,13 @@ NS_ASSUME_NONNULL_BEGIN @interface ASCollectionNode (Beta) - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator; + - (void)beginUpdates; + - (void)endUpdatesAnimated:(BOOL)animated; +- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASCollectionNode.h b/AsyncDisplayKit/ASCollectionNode.h index 49ca3ffe5a..1a8a70d98b 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/AsyncDisplayKit/ASCollectionNode.h @@ -52,7 +52,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Tuning parameters for a range type in the specified mode. * - * @param rangeMode The range mode to get the runing parameters for. + * @param rangeMode The range mode to get the running parameters for. * @param rangeType The range type to get the tuning parameters for. * * @returns A tuning parameter value for the given range type in the given mode. @@ -63,10 +63,10 @@ NS_ASSUME_NONNULL_BEGIN - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; /** - * Set the tuning parameters for a range type in the specigied mode. + * Set the tuning parameters for a range type in the specified mode. * * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeMode The range mode to set the runing parameters for. + * @param rangeMode The range mode to set the running parameters for. * @param rangeType The range type to set the tuning parameters for. * * @see ASLayoutRangeMode diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/AsyncDisplayKit/ASCollectionNode.mm index 68e7d9a341..d33ef7bb0d 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -10,7 +10,7 @@ #import "ASCollectionInternal.h" #import "ASCollectionViewLayoutFacilitatorProtocol.h" #import "ASDisplayNode+Subclasses.h" -#import "ASRangeController.h" +#import "ASRangeControllerUpdateRangeProtocol+Beta.h" #include @interface _ASCollectionPendingState : NSObject @@ -91,7 +91,7 @@ - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator { ASDisplayNodeViewBlock collectionViewBlock = ^UIView *{ - return [[ASCollectionView alloc] _initWithFrame:CGRectZero collectionViewLayout:layout layoutFacilitator:layoutFacilitator ownedByNode:YES]; + return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:layoutFacilitator ownedByNode:YES]; }; if (self = [super initWithViewBlock:collectionViewBlock]) { @@ -167,7 +167,7 @@ return (ASCollectionView *)[super view]; } -#if RangeControllerLoggingEnabled +#if ASRangeControllerLoggingEnabled - (void)visibilityDidChange:(BOOL)isVisible { [super visibilityDidChange:isVisible]; @@ -194,7 +194,12 @@ - (void)endUpdatesAnimated:(BOOL)animated { - [self.view.dataController endUpdatesAnimated:animated completion:nil]; + [self endUpdatesAnimated:animated completion:nil]; +} + +- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion +{ + [self.view.dataController endUpdatesAnimated:animated completion:completion]; } #pragma mark - ASCollectionView Forwards @@ -219,6 +224,11 @@ return [self.view.rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; } +- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; +{ + [self.view.rangeController updateCurrentRangeWithMode:rangeMode]; +} + - (void)reloadDataWithCompletion:(void (^)())completion { [self.view reloadDataWithCompletion:completion]; diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index f1475156e4..99a4bd0548 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -13,13 +13,12 @@ #import #import #import +#import @class ASCellNode; @class ASCollectionNode; @protocol ASCollectionDataSource; @protocol ASCollectionDelegate; -@protocol ASCollectionViewLayoutInspecting; -@protocol ASCollectionViewLayoutFacilitatorProtocol; NS_ASSUME_NONNULL_BEGIN @@ -77,7 +76,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Tuning parameters for a range type in the specified mode. * - * @param rangeMode The range mode to get the runing parameters for. + * @param rangeMode The range mode to get the running parameters for. * @param rangeType The range type to get the tuning parameters for. * * @returns A tuning parameter value for the given range type in the given mode. @@ -88,10 +87,10 @@ NS_ASSUME_NONNULL_BEGIN - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; /** - * Set the tuning parameters for a range type in the specigied mode. + * Set the tuning parameters for a range type in the specified mode. * * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeMode The range mode to set the runing parameters for. + * @param rangeMode The range mode to set the running parameters for. * @param rangeType The range type to set the tuning parameters for. * * @see ASLayoutRangeMode @@ -165,10 +164,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)reloadDataImmediately; +/** + * Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. + */ +- (void)waitUntilAllUpdatesAreCommitted; + /** * Registers the given kind of supplementary node for use in creating node-backed supplementary views. * - * @param kind The kind of supplementary node that will be requested through the data source. + * @param elementKind The kind of supplementary node that will be requested through the data source. * * @discussion Use this method to register support for the use of supplementary nodes in place of the default * `registerClass:forSupplementaryViewOfKind:withReuseIdentifier:` and `registerNib:forSupplementaryViewOfKind:withReuseIdentifier:` @@ -391,7 +395,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Indicator to lock the data source for data fetching in async mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception + * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception * due to the data access in async mode. * * @param collectionView The sender. @@ -400,7 +404,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Indicator to unlock the data source for data fetching in async mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception + * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception * due to the data access in async mode. * * @param collectionView The sender. @@ -477,17 +481,15 @@ NS_ASSUME_NONNULL_BEGIN @optional /** - * Passthrough support to UICollectionViewDelegateFlowLayout sectionInset behavior. - * - * @param collectionView The sender. - * @param collectionViewLayout The layout object requesting the information. - * @param section The index number of the section whose insets are needed. - * - * @discussion The same rules apply as the UICollectionView implementation, but this can also be used without a UICollectionViewFlowLayout. - * https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewDelegateFlowLayout_protocol/index.html#//apple_ref/occ/intfm/UICollectionViewDelegateFlowLayout/collectionView:layout:insetForSectionAtIndex: - * + * @discussion This method is deprecated and does nothing from 1.9.7 and up + * Previously it applies the section inset to every cells within the corresponding section. + * The expected behavior is to apply the section inset to the whole section rather than + * shrinking each cell individually. + * If you want this behavior, you can integrate your insets calculation into + * `constrainedSizeForNodeAtIndexPath` + * please file a github issue if you would like this to be restored. */ -- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section; +- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section __deprecated_msg("This method does nothing for 1.9.7+ due to incorrect implementation previously, see the header file for more information."); /** * Asks the delegate for the size of the header in the specified section. diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 77fc3dd60d..0835dcfbe1 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -14,11 +14,12 @@ #import "ASCollectionDataController.h" #import "ASCollectionViewLayoutController.h" #import "ASCollectionViewFlowLayoutInspector.h" -#import "ASCollectionViewLayoutFacilitatorProtocol.h" +#import "ASDisplayNodeExtras.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Beta.h" #import "ASInternalHelpers.h" #import "UICollectionViewLayout+ASConvenience.h" +#import "ASRangeControllerUpdateRangeProtocol+Beta.h" #import "_ASDisplayLayer.h" static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone; @@ -57,10 +58,40 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; @end +#pragma mark - +#pragma mark _ASCollectionViewNodeSizeUpdateContext + +/** + * This class contains all the nodes that have a new size and UICollectionView should requery them all at once. + * It is intended to be used strictly on main thread and is not thread safe. + */ +@interface _ASCollectionViewNodeSizeInvalidationContext : NSObject +/** + * It's possible that a node triggered multiple size changes before main thread has a chance to execute `requeryNodeSizes`. + * Therefore, a set is preferred here, to avoid asking ASDataController to search for index path of the same node multiple times. + */ +@property (nonatomic, strong) NSMutableSet *invalidatedNodes; +@property (nonatomic, assign) BOOL shouldAnimate; +@end + +@implementation _ASCollectionViewNodeSizeInvalidationContext + +- (instancetype)init +{ + self = [super init]; + if (self) { + _invalidatedNodes = [NSMutableSet set]; + _shouldAnimate = YES; + } + return self; +} + +@end + #pragma mark - #pragma mark ASCollectionView. -@interface ASCollectionView () { +@interface ASCollectionView () { ASCollectionViewProxy *_proxyDataSource; ASCollectionViewProxy *_proxyDelegate; @@ -75,12 +106,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; NSMutableArray *_batchUpdateBlocks; BOOL _asyncDataFetchingEnabled; - BOOL _asyncDelegateImplementsInsetSection; BOOL _asyncDelegateImplementsScrollviewDidScroll; - BOOL _collectionViewLayoutImplementsInsetSection; BOOL _asyncDataSourceImplementsConstrainedSizeForNode; BOOL _asyncDataSourceImplementsNodeBlockForItemAtIndexPath; - BOOL _queuedNodeSizeUpdate; + _ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only BOOL _isDeallocating; ASBatchContext *_batchContext; @@ -192,8 +221,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _superIsPendingDataLoad = YES; - _collectionViewLayoutImplementsInsetSection = [layout respondsToSelector:@selector(sectionInset)]; - _maxSizeForNodesConstrainedSize = self.bounds.size; // If the initial size is 0, expect a size change very soon which is part of the initial configuration // and should not trigger a relayout. @@ -268,6 +295,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [super reloadData]; } +- (void)waitUntilAllUpdatesAreCommitted +{ + ASDisplayNodeAssertMainThread(); + [_dataController waitUntilAllUpdatesAreCommitted]; +} + - (void)setDataSource:(id)dataSource { // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. @@ -330,12 +363,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (asyncDelegate == nil) { _asyncDelegate = nil; _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDelegateImplementsInsetSection = NO; _asyncDelegateImplementsScrollviewDidScroll = NO; } else { _asyncDelegate = asyncDelegate; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; - _asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0); _asyncDelegateImplementsScrollviewDidScroll = ([_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)] ? 1 : 0); } @@ -532,32 +563,37 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; + ASCellNode *cellNode = [cell node]; + cellNode.scrollView = collectionView; if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) { [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; } - ASCellNode *cellNode = [cell node]; + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + if (cellNode.neverShowPlaceholders) { [cellNode recursivelyEnsureDisplaySynchronously:YES]; } - if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(visibleNodeDidScroll:withCellFrame:))) { + if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:))) { [_cellsForVisibilityUpdates addObject:cell]; } } -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath +- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + + ASCellNode *cellNode = [cell node]; if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]) { - ASCellNode *node = ((_ASCollectionViewCell *)cell).node; - ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil."); - [_asyncDelegate collectionView:self didEndDisplayingNode:node forItemAtIndexPath:indexPath]; + ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); + [_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath]; + } + + if ([_cellsForVisibilityUpdates containsObject:cell]) { + [_cellsForVisibilityUpdates removeObject:cell]; } - [_cellsForVisibilityUpdates removeObject:cell]; - #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -565,10 +601,49 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath]; } #pragma clang diagnostic pop + + cellNode.scrollView = nil; } -#pragma mark - -#pragma mark Scroll Direction. + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + // If a scroll happenes the current range mode needs to go to full + ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; + if (ASInterfaceStateIncludesVisible(interfaceState)) { + [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; + } + + for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) { + // Only nodes that respond to the selector are added to _cellsForVisibilityUpdates + [[collectionCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged + inScrollView:scrollView + withCellFrame:collectionCell.frame]; + } + if (_asyncDelegateImplementsScrollviewDidScroll) { + [_asyncDelegate scrollViewDidScroll:scrollView]; + } +} + +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset +{ + _deceleratingVelocity = CGPointMake( + scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), + scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) + ); + + if (targetContentOffset != NULL) { + ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; + } + + if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { + [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; + } +} + + +#pragma mark - Scroll Direction. - (ASScrollDirection)scrollDirection { @@ -589,16 +664,16 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASScrollDirection scrollableDirections = [self scrollableDirections]; if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. - if (scrollVelocity.x > 0) { + if (scrollVelocity.x < 0.0) { direction |= ASScrollDirectionRight; - } else if (scrollVelocity.x < 0) { + } else if (scrollVelocity.x > 0.0) { direction |= ASScrollDirectionLeft; } } if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. - if (scrollVelocity.y > 0) { + if (scrollVelocity.y < 0.0) { direction |= ASScrollDirectionDown; - } else if (scrollVelocity.y < 0) { + } else if (scrollVelocity.y > 0.0) { direction |= ASScrollDirectionUp; } } @@ -622,10 +697,13 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (ASScrollDirection)nonFlowLayoutScrollableDirections { ASScrollDirection scrollableDirection = ASScrollDirectionNone; - if (self.contentSize.width > self.bounds.size.width) { // Can scroll horizontally. + CGFloat totalContentWidth = self.contentSize.width + self.contentInset.left + self.contentInset.right; + CGFloat totalContentHeight = self.contentSize.height + self.contentInset.top + self.contentInset.bottom; + + if (self.alwaysBounceHorizontal || totalContentWidth > self.bounds.size.width) { // Can scroll horizontally. scrollableDirection |= ASScrollDirectionHorizontalDirections; } - if (self.contentSize.height > self.bounds.size.height) { // Can scroll vertically. + if (self.alwaysBounceVertical || totalContentHeight > self.bounds.size.height) { // Can scroll vertically. scrollableDirection |= ASScrollDirectionVerticalDirections; } return scrollableDirection; @@ -645,7 +723,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (_ignoreMaxSizeChange) { _ignoreMaxSizeChange = NO; } else { - [self performBatchAnimated:NO updates:^{ + // This actually doesn't perform an animation, but prevents the transaction block from being processed in the + // data controller's prevent animation block that would interrupt an interrupted relayout happening in an animation block + // ie. ASCollectionView bounds change on rotation or multi-tasking split view resize. + [self performBatchAnimated:YES updates:^{ [_dataController relayoutAllNodes]; } completion:nil]; } @@ -656,38 +737,14 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } -#pragma mark - -#pragma mark Batch Fetching +#pragma mark - Batch Fetching -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset +- (ASBatchContext *)batchContext { - _deceleratingVelocity = CGPointMake( - scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), - scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) - ); - - if (targetContentOffset != NULL) { - [self handleBatchFetchScrollingToOffset:*targetContentOffset]; - } - - if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { - [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; - } + return _batchContext; } -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) { - ASCellNode *node = [collectionCell node]; - // Only nodes that respond to the selector are added to _cellsForVisibilityUpdates - [node visibleNodeDidScroll:scrollView withCellFrame:collectionCell.frame]; - } - if (_asyncDelegateImplementsScrollviewDidScroll) { - [_asyncDelegate scrollViewDidScroll:scrollView]; - } -} - -- (BOOL)shouldBatchFetch +- (BOOL)canBatchFetch { // if the delegate does not respond to this method, there is no point in starting to fetch BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]; @@ -698,16 +755,40 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +- (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - - if (![self shouldBatchFetch]) { + // Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided + if (changes == 0) { return; } - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { - [_batchContext beginBatchFetching]; + // Push this to the next runloop to be sure the scroll view has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); +} + +- (void)_checkForBatchFetching +{ + // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: + if (self.isDragging || self.isTracking) { + return; + } + + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; +} + +- (void)_beginBatchFetchingIfNeededWithScrollView:(UIScrollView *)scrollView forScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset +{ + if (ASDisplayShouldFetchBatchForScrollView(self, scrollDirection, contentOffset)) { + [self _beginBatchFetching]; + } +} + +- (void)_beginBatchFetching +{ + [_batchContext beginBatchFetching]; + if ([_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; }); @@ -764,7 +845,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (_asyncDataSourceImplementsConstrainedSizeForNode) { constrainedSize = [_asyncDataSource collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; } else { - CGSize maxSize = _maxSizeForNodesConstrainedSize; + CGSize maxSize = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero) ? self.bounds.size : _maxSizeForNodesConstrainedSize; if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) { maxSize.width = FLT_MAX; } else { @@ -773,25 +854,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; constrainedSize = ASSizeRangeMake(CGSizeZero, maxSize); } - UIEdgeInsets sectionInset = UIEdgeInsetsZero; - if (_collectionViewLayoutImplementsInsetSection) { - sectionInset = [(UICollectionViewFlowLayout *)self.collectionViewLayout sectionInset]; - } - - if (_asyncDelegateImplementsInsetSection) { - sectionInset = [(id)_asyncDelegate collectionView:self layout:self.collectionViewLayout insetForSectionAtIndex:indexPath.section]; - } - - constrainedSize.min.height = MAX(0, constrainedSize.min.height - sectionInset.top - sectionInset.bottom); - constrainedSize.min.width = MAX(0, constrainedSize.min.width - sectionInset.left - sectionInset.right); - //ignore insets for FLT_MAX so FLT_MAX can be compared against - if (constrainedSize.max.width - FLT_EPSILON < FLT_MAX) { - constrainedSize.max.width = MAX(0, constrainedSize.max.width - sectionInset.left - sectionInset.right); - } - if (constrainedSize.max.height - FLT_EPSILON < FLT_MAX) { - constrainedSize.max.height = MAX(0, constrainedSize.max.height - sectionInset.top - sectionInset.bottom); - } - return constrainedSize; } @@ -870,7 +932,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController { ASDisplayNodeAssertMainThread(); - return [self indexPathsForVisibleItems]; + // Calling visibleNodeIndexPathsForRangeController: will trigger UIKit to call reloadData if it never has, which can result + // in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast. + BOOL isZeroSized = CGRectEqualToRect(self.bounds, CGRectZero); + return isZeroSized ? @[] : [self indexPathsForVisibleItems]; } - (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController @@ -881,15 +946,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController { - ASCollectionNode *collectionNode = self.collectionNode; - if (collectionNode) { - return collectionNode.interfaceState; - } else { - // Until we can always create an associated ASCollectionNode without a retain cycle, - // we might be on our own to try to guess if we're visible. The node normally - // handles this even if it is the root / directly added to the view hierarchy. - return (self.window != nil ? ASInterfaceStateVisible : ASInterfaceStateNone); - } + return ASInterfaceStateForDisplayNode(self.collectionNode, self.window); } - (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths @@ -921,13 +978,17 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } + NSUInteger numberOfUpdateBlocks = _batchUpdateBlocks.count; ASPerformBlockWithoutAnimation(!animated, ^{ [_layoutFacilitator collectionViewWillPerformBatchUpdates]; [super performBatchUpdates:^{ for (dispatch_block_t block in _batchUpdateBlocks) { block(); } - } completion:completion]; + } completion:^(BOOL finished){ + [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdateBlocks]; + if (completion) { completion(finished); } + }]; }); [_batchUpdateBlocks removeAllObjects]; @@ -950,6 +1011,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; [UIView performWithoutAnimation:^{ [super insertItemsAtIndexPaths:indexPaths]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; }]; } } @@ -970,6 +1032,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; [UIView performWithoutAnimation:^{ [super deleteItemsAtIndexPaths:indexPaths]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; }]; } } @@ -990,6 +1053,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; [UIView performWithoutAnimation:^{ [super insertSections:indexSet]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; }]; } } @@ -1010,6 +1074,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; [UIView performWithoutAnimation:^{ [super deleteSections:indexSet]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; }]; } } @@ -1020,43 +1085,73 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { ASDisplayNodeAssertMainThread(); - if (!sizeChanged || _queuedNodeSizeUpdate) { + if (!sizeChanged) { return; } - _queuedNodeSizeUpdate = YES; - [self performSelector:@selector(requeryNodeSizes) - withObject:nil - afterDelay:0 - inModes:@[ NSRunLoopCommonModes ]]; + BOOL queued = (_queuedNodeSizeInvalidationContext != nil); + if (!queued) { + _queuedNodeSizeInvalidationContext = [[_ASCollectionViewNodeSizeInvalidationContext alloc] init]; + + __weak __typeof__(self) weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + __typeof__(self) strongSelf = weakSelf; + if (strongSelf) { + [strongSelf requeryNodeSizes]; + } + }); + } + + [_queuedNodeSizeInvalidationContext.invalidatedNodes addObject:node]; + + // Check if this node or one of its subnodes can be animated. + // If the context is already non-animated, don't bother checking this node. + if (_queuedNodeSizeInvalidationContext.shouldAnimate) { + BOOL (^shouldNotAnimateBlock)(ASDisplayNode *) = ^BOOL(ASDisplayNode * _Nonnull node) { + return node.shouldAnimateSizeChanges == NO; + }; + if (ASDisplayNodeFindFirstNode(node, shouldNotAnimateBlock) != nil) { + // One single non-animated cell node causes the whole context to be non-animated + _queuedNodeSizeInvalidationContext.shouldAnimate = NO; + } + } } // Cause UICollectionView to requery for the new size of all nodes - (void)requeryNodeSizes { - _queuedNodeSizeUpdate = NO; + ASDisplayNodeAssertMainThread(); + NSSet *nodes = _queuedNodeSizeInvalidationContext.invalidatedNodes; + NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodes.count]; + for (ASCellNode *node in nodes) { + NSIndexPath *indexPath = [self indexPathForNode:node]; + if (indexPath != nil) { + [indexPaths addObject:indexPath]; + } + } - [super performBatchUpdates:^{} completion:nil]; + if (indexPaths.count > 0) { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; + + ASPerformBlockWithoutAnimation(!_queuedNodeSizeInvalidationContext.shouldAnimate, ^{ + // Perform an empty update transaction here to trigger UICollectionView to requery row sizes and layout its subviews again + [super performBatchUpdates:^{} completion:nil]; + }); + } + + _queuedNodeSizeInvalidationContext = nil; } #pragma mark - Memory Management - (void)clearContents { - for (NSArray *section in [_dataController completedNodes]) { - for (ASDisplayNode *node in section) { - [node exitInterfaceState:ASInterfaceStateDisplay]; - } - } + [_rangeController clearContents]; } - (void)clearFetchedData { - for (NSArray *section in [_dataController completedNodes]) { - for (ASDisplayNode *node in section) { - [node exitInterfaceState:ASInterfaceStateFetchData]; - } - } + [_rangeController clearFetchedData]; } #pragma mark - _ASDisplayView behavior substitutions @@ -1078,6 +1173,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (!visible && node.inHierarchy) { [node __exitHierarchy]; } + + // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their + // their update in the layout pass + if (![node supportsRangeManagedInterfaceState]) { + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + } } #pragma mark - UICollectionView dead-end intercepts diff --git a/AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h b/AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h index 514df97448..38f43b71a8 100644 --- a/AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h +++ b/AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h @@ -6,8 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#ifndef ASCollectionViewLayoutFacilitatorProtocol_h -#define ASCollectionViewLayoutFacilitatorProtocol_h +#pragma once /** * This facilitator protocol is intended to help Layout to better @@ -18,8 +17,8 @@ /** * Inform that the collectionView is editing the cells at a list of indexPaths * - * @param indexPaths, an array of NSIndexPath objects of cells being/will be edited. - * @param isBatched, indicates whether the editing operation will be batched by the collectionView + * @param indexPaths an array of NSIndexPath objects of cells being/will be edited. + * @param isBatched indicates whether the editing operation will be batched by the collectionView * * NOTE: when isBatched, used in combination with -collectionViewWillPerformBatchUpdates */ @@ -28,10 +27,10 @@ /** * Inform that the collectionView is editing the sections at a set of indexes * - * @param indexes, an NSIndexSet of section indexes being/will be edited. - * @param isBatched, indicates whether the editing operation will be batched by the collectionView + * @param indexes an NSIndexSet of section indexes being/will be edited. + * @param batched indicates whether the editing operation will be batched by the collectionView * - * NOTE: when isBatched, used in combination with -collectionViewWillPerformBatchUpdates + * NOTE: when batched, used in combination with -collectionViewWillPerformBatchUpdates */ - (void)collectionViewWillEditSectionsAtIndexSet:(NSIndexSet *)indexes batched:(BOOL)batched; @@ -41,5 +40,3 @@ - (void)collectionViewWillPerformBatchUpdates; @end - -#endif /* ASCollectionViewLayoutFacilitatorProtocol_h */ diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/AsyncDisplayKit/ASContextTransitioning.h index 694ab6bf83..037f9a64d3 100644 --- a/AsyncDisplayKit/ASContextTransitioning.h +++ b/AsyncDisplayKit/ASContextTransitioning.h @@ -14,7 +14,7 @@ extern NSString * const ASTransitionContextToLayoutKey; @protocol ASContextTransitioning /** - @abstreact Defines if the given transition is animated + @abstract Defines if the given transition is animated */ - (BOOL)isAnimated; diff --git a/AsyncDisplayKit/ASControlNode+Subclasses.h b/AsyncDisplayKit/ASControlNode+Subclasses.h index 107de031e0..003037532f 100644 --- a/AsyncDisplayKit/ASControlNode+Subclasses.h +++ b/AsyncDisplayKit/ASControlNode+Subclasses.h @@ -7,6 +7,7 @@ */ #import "ASControlNode.h" +#import "ASDisplayNode+Subclasses.h" NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/ASControlNode.h b/AsyncDisplayKit/ASControlNode.h index 50eee12902..24e47b3e72 100644 --- a/AsyncDisplayKit/ASControlNode.h +++ b/AsyncDisplayKit/ASControlNode.h @@ -41,7 +41,7 @@ typedef NS_OPTIONS(NSUInteger, ASControlState) { ASControlStateNormal = 0, ASControlStateHighlighted = 1 << 0, // used when ASControlNode isHighlighted is set ASControlStateDisabled = 1 << 1, - ASControlStateSelected = 1 << 2, // used when ASControlNode isSeleted is set + ASControlStateSelected = 1 << 2, // used when ASControlNode isSelected is set ASControlStateReserved = 0xFF000000 // flags reserved for internal framework use }; diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index 6d951c8dbc..8a912a7eaa 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -9,6 +9,8 @@ #import "ASControlNode.h" #import "ASControlNode+Subclasses.h" #import "ASThread.h" +#import "ASDisplayNodeExtras.h" +#import "ASImageNode.h" // UIControl allows dragging some distance outside of the control itself during // tracking. This value depends on the device idiom (25 or 70 points), so @@ -69,11 +71,16 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v @end -#pragma mark - +static BOOL _enableHitTestDebug = NO; + @implementation ASControlNode +{ + ASImageNode *_debugHighlightOverlay; +} #pragma mark - Lifecycle -- (id)init + +- (instancetype)init { if (!(self = [super init])) return nil; @@ -97,6 +104,16 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v } #endif +- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled +{ + [super setUserInteractionEnabled:userInteractionEnabled]; + self.isAccessibilityElement = userInteractionEnabled; +} + + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" + #pragma mark - ASDisplayNode Overrides - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { @@ -213,6 +230,8 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v withEvent:event]; } +#pragma clang diagnostic pop + - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { // If we're interested in touches, this is a tap (the only gesture we care about) and passed -hitTest for us, then no, you may not begin. Sir. @@ -240,6 +259,17 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v if (!_controlEventDispatchTable) { _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. + + // only show tap-able areas for views with 1 or more addTarget:action: pairs + if (_enableHitTestDebug) { + + // add a highlight overlay node with area of ASControlNode + UIEdgeInsets + self.clipsToBounds = NO; + _debugHighlightOverlay = [[ASImageNode alloc] init]; + _debugHighlightOverlay.zPosition = 1000; // CALayer doesn't have -moveSublayerToFront, but this will ensure we're over the top of any siblings. + _debugHighlightOverlay.layerBacked = YES; + [self addSubnode:_debugHighlightOverlay]; + } } // Enumerate the events in the mask, adding the target-action pair for each control event included in controlEventMask @@ -248,26 +278,27 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { // Do we already have an event table for this control event? id eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent); - NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:eventKey]; + NSMapTable *eventDispatchTable = _controlEventDispatchTable[eventKey]; // Create it if necessary. if (!eventDispatchTable) { // Create the dispatch table for this event. eventDispatchTable = [NSMapTable weakToStrongObjectsMapTable]; - [_controlEventDispatchTable setObject:eventDispatchTable forKey:eventKey]; + _controlEventDispatchTable[eventKey] = eventDispatchTable; } // Have we seen this target before for this event? - NSMutableArray *targetActions = [eventDispatchTable objectForKey:target]; + NSMutableSet *targetActions = [eventDispatchTable objectForKey:target]; if (!targetActions) { - // Nope. Create an actions array for it. - targetActions = [[NSMutableArray alloc] initWithCapacity:kASControlNodeActionDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. + // Nope. Create an action set for it. + targetActions = [[NSMutableSet alloc] initWithCapacity:kASControlNodeActionDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. [eventDispatchTable setObject:targetActions forKey:target]; } // Add the action message. - // Note that bizarrely enough UIControl (at least according to the docs) supports duplicate target-action pairs for a particular control event, so we replicate that behavior. + // UIControl does not support duplicate target-action-events entries, so we replicate that behavior. + // See: https://github.com/facebook/AsyncDisplayKit/files/205466/DuplicateActionsTest.playground.zip [targetActions addObject:NSStringFromSelector(action)]; }); @@ -282,7 +313,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v ASDN::MutexLocker l(_controlLock); // Grab the event dispatch table for this event. - NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)]; + NSMapTable *eventDispatchTable = _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]; if (!eventDispatchTable) return nil; @@ -319,7 +350,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { // Grab the dispatch table for this event (if we have it). id eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent); - NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:eventKey]; + NSMapTable *eventDispatchTable = _controlEventDispatchTable[eventKey]; if (!eventDispatchTable) return; @@ -352,7 +383,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v if (!target) { // Look at every target, removing target-pairs that have action (or all of its actions). - for (id aTarget in eventDispatchTable) + for (id aTarget in [eventDispatchTable copy]) removeActionFromTarget(aTarget, action); } else @@ -372,7 +403,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v (ASControlNodeEvent controlEvent) { // Use a copy to itereate, the action perform could call remove causing a mutation crash. - NSMapTable *eventDispatchTable = [[_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)] copy]; + NSMapTable *eventDispatchTable = [_controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)] copy]; // For each target interested in this event... for (id target in eventDispatchTable) @@ -404,7 +435,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v id _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent) { - return [NSNumber numberWithInteger:controlEvent]; + return @(controlEvent); } void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent)) @@ -513,4 +544,135 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); } #endif //TARGET_OS_TV +#pragma mark - Debug +// Layout method required when _enableHitTestDebug is enabled. +- (void)layout +{ + [super layout]; + + if (_debugHighlightOverlay) { + + // Even if our parents don't have clipsToBounds set and would allow us to display the debug overlay, UIKit event delivery (hitTest:) + // will not search sub-hierarchies if one of our parents does not return YES for pointInside:. In such a scenario, hitTestSlop + // may not be able to expand the tap target as much as desired without also setting some hitTestSlop on the limiting parents. + CGRect intersectRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]); + UIRectEdge clippedEdges = UIRectEdgeNone; + UIRectEdge clipsToBoundsClippedEdges = UIRectEdgeNone; + CALayer *layer = self.layer; + CALayer *intersectLayer = layer; + CALayer *intersectSuperlayer = layer.superlayer; + + // Stop climbing if we encounter a UIScrollView, as its offset bounds origin may make it seem like our events will be clipped when + // scrolling will actually reveal them (because this process will not re-run due to scrolling) + while (intersectSuperlayer && ![intersectSuperlayer.delegate respondsToSelector:@selector(contentOffset)]) { + // Get our parent's tappable bounds. If the parent has an associated node, consider hitTestSlop, as it will extend its pointInside:. + CGRect parentHitRect = intersectSuperlayer.bounds; + BOOL parentClipsToBounds = NO; + + ASDisplayNode *parentNode = ASLayerToDisplayNode(intersectSuperlayer); + if (parentNode) { + UIEdgeInsets parentSlop = [parentNode hitTestSlop]; + + // if parent has a hitTestSlop as well, we need to account for the fact that events will be routed towards us in that area too. + if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, parentSlop)) { + parentClipsToBounds = parentNode.clipsToBounds; + // if the parent is clipping, this will prevent us from showing the overlay outside that area. + // in this case, we will make the overlay smaller so that the special highlight to indicate the overlay + // cannot accurately display the true tappable area is shown. + if (!parentClipsToBounds) { + parentHitRect = UIEdgeInsetsInsetRect(parentHitRect, [parentNode hitTestSlop]); + } + } + } + + // Convert our current rectangle to parent coordinates, and intersect with the parent's hit rect. + CGRect intersectRectInParentCoordinates = [intersectSuperlayer convertRect:intersectRect fromLayer:intersectLayer]; + intersectRect = CGRectIntersection(parentHitRect, intersectRectInParentCoordinates); + if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) { + clippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates + parentRect:parentHitRect rectEdge:clippedEdges]; + if (parentClipsToBounds) { + clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates + parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges]; + } + } + + // Advance up the tree. + intersectLayer = intersectSuperlayer; + intersectSuperlayer = intersectLayer.superlayer; + } + + CGRect finalRect = [intersectLayer convertRect:intersectRect toLayer:layer]; + UIColor *fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.4]; + + // determine if edges are clipped + if (clippedEdges == UIRectEdgeNone) { + _debugHighlightOverlay.backgroundColor = fillColor; + } else { + const CGFloat borderWidth = 2.0; + UIColor *borderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8]; + UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; + CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0); + UIGraphicsBeginImageContext(imgRect.size); + + [fillColor setFill]; + UIRectFill(imgRect); + + [self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect]; + [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect]; + + UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth); + _debugHighlightOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets + resizingMode:UIImageResizingModeStretch]; + _debugHighlightOverlay.backgroundColor = nil; + } + + _debugHighlightOverlay.frame = finalRect; + } +} + +- (UIRectEdge)setEdgesOfIntersectionForChildRect:(CGRect)childRect parentRect:(CGRect)parentRect rectEdge:(UIRectEdge)rectEdge +{ + if (childRect.origin.y < parentRect.origin.y) { + rectEdge |= UIRectEdgeTop; + } + if (childRect.origin.x < parentRect.origin.x) { + rectEdge |= UIRectEdgeLeft; + } + if (CGRectGetMaxY(childRect) > CGRectGetMaxY(parentRect)) { + rectEdge |= UIRectEdgeBottom; + } + if (CGRectGetMaxX(childRect) > CGRectGetMaxX(parentRect)) { + rectEdge |= UIRectEdgeRight; + } + + return rectEdge; +} + +- (void)drawEdgeIfClippedWithEdges:(UIRectEdge)rectEdge color:(UIColor *)color borderWidth:(CGFloat)borderWidth imgRect:(CGRect)imgRect +{ + [color setFill]; + + if (rectEdge & UIRectEdgeTop) { + UIRectFill(CGRectMake(0.0, 0.0, imgRect.size.width, borderWidth)); + } + if (rectEdge & UIRectEdgeLeft) { + UIRectFill(CGRectMake(0.0, 0.0, borderWidth, imgRect.size.height)); + } + if (rectEdge & UIRectEdgeBottom) { + UIRectFill(CGRectMake(0.0, imgRect.size.height - borderWidth, imgRect.size.width, borderWidth)); + } + if (rectEdge & UIRectEdgeRight) { + UIRectFill(CGRectMake(imgRect.size.width - borderWidth, 0.0, borderWidth, imgRect.size.height)); + } +} + ++ (void)setEnableHitTestDebug:(BOOL)enable +{ + _enableHitTestDebug = enable; +} + @end diff --git a/AsyncDisplayKit/ASControlNode.mm.orig b/AsyncDisplayKit/ASControlNode.mm.orig new file mode 100644 index 0000000000..5c860fe360 --- /dev/null +++ b/AsyncDisplayKit/ASControlNode.mm.orig @@ -0,0 +1,683 @@ +/* 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 "ASControlNode.h" +#import "ASControlNode+Subclasses.h" +#import "ASThread.h" +#import "ASDisplayNodeExtras.h" +#import "ASImageNode.h" + +// UIControl allows dragging some distance outside of the control itself during +// tracking. This value depends on the device idiom (25 or 70 points), so +// so replicate that effect with the same values here for our own controls. +#define kASControlNodeExpandedInset (([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? -25.0f : -70.0f) + +// Initial capacities for dispatch tables. +#define kASControlNodeEventDispatchTableInitialCapacity 4 +#define kASControlNodeActionDispatchTableInitialCapacity 4 + +@interface ASControlNode () +{ +@private + ASDN::RecursiveMutex _controlLock; + + // Control Attributes + BOOL _enabled; + BOOL _highlighted; + + // Tracking + BOOL _tracking; + BOOL _touchInside; + + // Target Messages. + /* + The table structure is as follows: + + { + AnEvent -> { + target1 -> (action1, ...) + target2 -> (action1, ...) + ... + } + ... + } + */ + NSMutableDictionary *_controlEventDispatchTable; +} + +// Read-write overrides. +@property (nonatomic, readwrite, assign, getter=isTracking) BOOL tracking; +@property (nonatomic, readwrite, assign, getter=isTouchInside) BOOL touchInside; + +/** + @abstract Returns a key to be used in _controlEventDispatchTable that identifies the control event. + @param controlEvent A control event. + @result A key for use in _controlEventDispatchTable. + */ +id _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent); + +/** + @abstract Enumerates the ASControlNode events included mask, invoking the block for each event. + @param mask An ASControlNodeEvent mask. + @param block The block to be invoked for each ASControlNodeEvent included in mask. + @param anEvent An even that is included in mask. + */ +void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent)); + +@end + +static BOOL _enableHitTestDebug = NO; + +@implementation ASControlNode +{ + ASImageNode *_debugHighlightOverlay; +} + +#pragma mark - Lifecycle + +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + _enabled = YES; + + // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. + self.userInteractionEnabled = NO; + + return self; +} + +<<<<<<< HEAD +#if TARGET_OS_TV +- (void)didLoad +{ + //On tvOS all control views, such as buttons, interact with the focus system even if they don't have a target set on them. Here we add our own internal tap gesture to handle this behaviour. + self.userInteractionEnabled = YES; + UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pressDown)]; + tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)]; + [self.view addGestureRecognizer:tapGestureRec]; +} +#endif +======= +- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled +{ + [super setUserInteractionEnabled:userInteractionEnabled]; + self.isAccessibilityElement = userInteractionEnabled; +} + + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" +>>>>>>> master + +#pragma mark - ASDisplayNode Overrides +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + // If we're not interested in touches, we have nothing to do. + if (!self.enabled) + return; + + ASControlNodeEvent controlEventMask = 0; + + // If we get more than one touch down on us, cancel. + // Additionally, if we're already tracking a touch, a second touch beginning is cause for cancellation. + if ([touches count] > 1 || self.tracking) + { + self.tracking = NO; + self.touchInside = NO; + [self cancelTrackingWithEvent:event]; + controlEventMask |= ASControlNodeEventTouchCancel; + } + else + { + // Otherwise, begin tracking. + self.tracking = YES; + + // No need to check bounds on touchesBegan as we wouldn't get the call if it wasn't in our bounds. + self.touchInside = YES; + self.highlighted = YES; + + UITouch *theTouch = [touches anyObject]; + [self beginTrackingWithTouch:theTouch withEvent:event]; + + // Send the appropriate touch-down control event depending on how many times we've been tapped. + controlEventMask |= (theTouch.tapCount == 1) ? ASControlNodeEventTouchDown : ASControlNodeEventTouchDownRepeat; + } + + [self sendActionsForControlEvents:controlEventMask withEvent:event]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event +{ + // If we're not interested in touches, we have nothing to do. + if (!self.enabled) + return; + + NSParameterAssert([touches count] == 1); + UITouch *theTouch = [touches anyObject]; + CGPoint touchLocation = [theTouch locationInView:self.view]; + + // Update our touchInside state. + BOOL dragIsInsideBounds = [self pointInside:touchLocation withEvent:nil]; + + // Update our highlighted state. + CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); + BOOL dragIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); + self.touchInside = dragIsInsideExpandedBounds; + self.highlighted = dragIsInsideExpandedBounds; + + // Note we are continuing to track the touch. + [self continueTrackingWithTouch:theTouch withEvent:event]; + + [self sendActionsForControlEvents:(dragIsInsideBounds ? ASControlNodeEventTouchDragInside : ASControlNodeEventTouchDragOutside) + withEvent:event]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event +{ + // If we're not interested in touches, we have nothing to do. + if (!self.enabled) + return; + + // We're no longer tracking and there is no touch to be inside. + self.tracking = NO; + self.touchInside = NO; + self.highlighted = NO; + + // Note that we've cancelled tracking. + [self cancelTrackingWithEvent:event]; + + // Send the cancel event. + [self sendActionsForControlEvents:ASControlNodeEventTouchCancel + withEvent:event]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + // If we're not interested in touches, we have nothing to do. + if (!self.enabled) + return; + + // On iPhone 6s, iOS 9.2 (and maybe other versions) sometimes calls -touchesEnded:withEvent: + // twice on the view for one call to -touchesBegan:withEvent:. On ASControlNode, it used to + // trigger an action twice unintentionally. Now, we ignore that event if we're not in a tracking + // state in order to have a correct behavior. + // It might be related to that issue: http://www.openradar.me/22910171 + if (!self.tracking) + return; + + NSParameterAssert([touches count] == 1); + UITouch *theTouch = [touches anyObject]; + CGPoint touchLocation = [theTouch locationInView:self.view]; + + // Update state. + self.tracking = NO; + self.touchInside = NO; + self.highlighted = NO; + + // Note that we've ended tracking. + [self endTrackingWithTouch:theTouch withEvent:event]; + + // Send the appropriate touch-up control event. + CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); + BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); + + [self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside) + withEvent:event]; +} + +#pragma clang diagnostic pop + +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer +{ + // If we're interested in touches, this is a tap (the only gesture we care about) and passed -hitTest for us, then no, you may not begin. Sir. + if (self.enabled && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && gestureRecognizer.view != self.view) { + UITapGestureRecognizer *tapRecognizer = (UITapGestureRecognizer *)gestureRecognizer; + // Allow double-tap gestures + return tapRecognizer.numberOfTapsRequired != 1; + } + + // Otherwise, go ahead. :] + return YES; +} + +#pragma mark - Action Messages +- (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask +{ + NSParameterAssert(action); + NSParameterAssert(controlEventMask != 0); + + ASDN::MutexLocker l(_controlLock); + + // Convert nil to [NSNull null] so that it can be used as a key for NSMapTable. + if (!target) + target = [NSNull null]; + + if (!_controlEventDispatchTable) { + _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. + + // only show tap-able areas for views with 1 or more addTarget:action: pairs + if (_enableHitTestDebug) { + + // add a highlight overlay node with area of ASControlNode + UIEdgeInsets + self.clipsToBounds = NO; + _debugHighlightOverlay = [[ASImageNode alloc] init]; + _debugHighlightOverlay.zPosition = 1000; // CALayer doesn't have -moveSublayerToFront, but this will ensure we're over the top of any siblings. + _debugHighlightOverlay.layerBacked = YES; + [self addSubnode:_debugHighlightOverlay]; + } + } + + // Enumerate the events in the mask, adding the target-action pair for each control event included in controlEventMask + _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ + (ASControlNodeEvent controlEvent) + { + // Do we already have an event table for this control event? + id eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent); + NSMapTable *eventDispatchTable = _controlEventDispatchTable[eventKey]; + // Create it if necessary. + if (!eventDispatchTable) + { + // Create the dispatch table for this event. + eventDispatchTable = [NSMapTable weakToStrongObjectsMapTable]; + _controlEventDispatchTable[eventKey] = eventDispatchTable; + } + + // Have we seen this target before for this event? + NSMutableSet *targetActions = [eventDispatchTable objectForKey:target]; + if (!targetActions) + { + // Nope. Create an action set for it. + targetActions = [[NSMutableSet alloc] initWithCapacity:kASControlNodeActionDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. + [eventDispatchTable setObject:targetActions forKey:target]; + } + + // Add the action message. + // UIControl does not support duplicate target-action-events entries, so we replicate that behavior. + // See: https://github.com/facebook/AsyncDisplayKit/files/205466/DuplicateActionsTest.playground.zip + [targetActions addObject:NSStringFromSelector(action)]; + }); + + self.userInteractionEnabled = YES; +} + +- (NSArray *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)controlEvent +{ + NSParameterAssert(target); + NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents); + + ASDN::MutexLocker l(_controlLock); + + // Grab the event dispatch table for this event. + NSMapTable *eventDispatchTable = _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]; + if (!eventDispatchTable) + return nil; + + // Return the actions for this target. + return [eventDispatchTable objectForKey:target]; +} + +- (NSSet *)allTargets +{ + ASDN::MutexLocker l(_controlLock); + + NSMutableSet *targets = [[NSMutableSet alloc] init]; + + // Look at each event... + for (NSMapTable *eventDispatchTable in [_controlEventDispatchTable allValues]) + { + // and each event's targets... + for (id target in eventDispatchTable) + [targets addObject:target]; + } + + return targets; +} + +- (void)removeTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask +{ + NSParameterAssert(controlEventMask != 0); + + ASDN::MutexLocker l(_controlLock); + + // Enumerate the events in the mask, removing the target-action pair for each control event included in controlEventMask. + _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ + (ASControlNodeEvent controlEvent) + { + // Grab the dispatch table for this event (if we have it). + id eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent); + NSMapTable *eventDispatchTable = _controlEventDispatchTable[eventKey]; + if (!eventDispatchTable) + return; + + void (^removeActionFromTarget)(id targetKey, SEL action) = ^ + (id aTarget, SEL theAction) + { + // Grab the targetActions for this target. + NSMutableArray *targetActions = [eventDispatchTable objectForKey:aTarget]; + + // Remove action if we have it. + if (theAction) + [targetActions removeObject:NSStringFromSelector(theAction)]; + // Or all actions if not. + else + [targetActions removeAllObjects]; + + // If there are no actions left, remove this target entry. + if ([targetActions count] == 0) + { + [eventDispatchTable removeObjectForKey:aTarget]; + + // If there are no targets for this event anymore, remove it. + if ([eventDispatchTable count] == 0) + [_controlEventDispatchTable removeObjectForKey:eventKey]; + } + }; + + + // Unlike addTarget:, if target is nil here we remove all targets with action. + if (!target) + { + // Look at every target, removing target-pairs that have action (or all of its actions). + for (id aTarget in [eventDispatchTable copy]) + removeActionFromTarget(aTarget, action); + } + else + removeActionFromTarget(target, action); + }); +} + +#pragma mark - +- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event +{ + NSParameterAssert(controlEvents != 0); + + ASDN::MutexLocker l(_controlLock); + + // Enumerate the events in the mask, invoking the target-action pairs for each. + _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^ + (ASControlNodeEvent controlEvent) + { + // Use a copy to itereate, the action perform could call remove causing a mutation crash. + NSMapTable *eventDispatchTable = [_controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)] copy]; + + // For each target interested in this event... + for (id target in eventDispatchTable) + { + NSArray *targetActions = [eventDispatchTable objectForKey:target]; + + // Invoke each of the actions on target. + for (NSString *actionMessage in targetActions) + { + SEL action = NSSelectorFromString(actionMessage); + id responder = target; + + // NSNull means that a nil target was set, so start at self and travel the responder chain + if (responder == [NSNull null]) { + // if the target cannot perform the action, travel the responder chain to try to find something that does + responder = [self.view targetForAction:action withSender:self]; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [responder performSelector:action withObject:self withObject:event]; +#pragma clang diagnostic pop + } + } + }); +} + +#pragma mark - Convenience + +id _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent) +{ + return @(controlEvent); +} + +void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent)) +{ + // Start with our first event (touch down) and work our way up to the last event (touch cancel) + for (ASControlNodeEvent thisEvent = ASControlNodeEventTouchDown; thisEvent <= ASControlNodeEventTouchCancel; thisEvent <<= 1) + { + // If it's included in the mask, invoke the block. + if ((mask & thisEvent) == thisEvent) + block(thisEvent); + } +} + +#pragma mark - For Subclasses +- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent +{ + return YES; +} + +- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent +{ + return YES; +} + +- (void)cancelTrackingWithEvent:(UIEvent *)touchEvent +{ +} + +- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent +{ +} + +<<<<<<< HEAD +#if TARGET_OS_TV +#pragma mark - tvOS +- (void)pressDown +{ + [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationCurveLinear animations:^{ + [self setPressedState]; + } completion:^(BOOL finished) { + if (finished) { + [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationCurveLinear animations:^{ + [self setFocusedState]; + } completion:nil]; + } + }]; +} + +- (BOOL)canBecomeFocused +{ + return YES; +} + +- (BOOL)shouldUpdateFocusInContext:(nonnull UIFocusUpdateContext *)context +{ + return YES; +} + +- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator +{ + //FIXME: This is never valid inside an ASCellNode + if (context.nextFocusedView && context.nextFocusedView == self.view) { + //Focused + [coordinator addCoordinatedAnimations:^{ + [self setFocusedState]; + } completion:nil]; + } else{ + //Not focused + [coordinator addCoordinatedAnimations:^{ + [self setDefaultState]; + } completion:nil]; + } +} + +- (void)setFocusedState +{ + CALayer *layer = self.layer; + layer.shadowOffset = CGSizeMake(2, 10); + [self applyDefaultShadowProperties: layer]; + self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1); +} + +- (void)setPressedState +{ + CALayer *layer = self.layer; + layer.shadowOffset = CGSizeMake(2, 2); + [self applyDefaultShadowProperties: layer]; + self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); +} + +- (void)applyDefaultShadowProperties:(CALayer *)layer +{ + layer.shadowColor = [UIColor blackColor].CGColor; + layer.shadowRadius = 12.0; + layer.shadowOpacity = 0.45; + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; +} + +- (void)setDefaultState +{ + CALayer *layer = self.layer; + layer.shadowOffset = CGSizeZero; + layer.shadowColor = [UIColor blackColor].CGColor; + layer.shadowRadius = 0; + layer.shadowOpacity = 0; + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); +} +#endif //TARGET_OS_TV +======= +#pragma mark - Debug +// Layout method required when _enableHitTestDebug is enabled. +- (void)layout +{ + [super layout]; + + if (_debugHighlightOverlay) { + + // Even if our parents don't have clipsToBounds set and would allow us to display the debug overlay, UIKit event delivery (hitTest:) + // will not search sub-hierarchies if one of our parents does not return YES for pointInside:. In such a scenario, hitTestSlop + // may not be able to expand the tap target as much as desired without also setting some hitTestSlop on the limiting parents. + CGRect intersectRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]); + UIRectEdge clippedEdges = UIRectEdgeNone; + UIRectEdge clipsToBoundsClippedEdges = UIRectEdgeNone; + CALayer *layer = self.layer; + CALayer *intersectLayer = layer; + CALayer *intersectSuperlayer = layer.superlayer; + + // Stop climbing if we encounter a UIScrollView, as its offset bounds origin may make it seem like our events will be clipped when + // scrolling will actually reveal them (because this process will not re-run due to scrolling) + while (intersectSuperlayer && ![intersectSuperlayer.delegate respondsToSelector:@selector(contentOffset)]) { + // Get our parent's tappable bounds. If the parent has an associated node, consider hitTestSlop, as it will extend its pointInside:. + CGRect parentHitRect = intersectSuperlayer.bounds; + BOOL parentClipsToBounds = NO; + + ASDisplayNode *parentNode = ASLayerToDisplayNode(intersectSuperlayer); + if (parentNode) { + UIEdgeInsets parentSlop = [parentNode hitTestSlop]; + + // if parent has a hitTestSlop as well, we need to account for the fact that events will be routed towards us in that area too. + if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, parentSlop)) { + parentClipsToBounds = parentNode.clipsToBounds; + // if the parent is clipping, this will prevent us from showing the overlay outside that area. + // in this case, we will make the overlay smaller so that the special highlight to indicate the overlay + // cannot accurately display the true tappable area is shown. + if (!parentClipsToBounds) { + parentHitRect = UIEdgeInsetsInsetRect(parentHitRect, [parentNode hitTestSlop]); + } + } + } + + // Convert our current rectangle to parent coordinates, and intersect with the parent's hit rect. + CGRect intersectRectInParentCoordinates = [intersectSuperlayer convertRect:intersectRect fromLayer:intersectLayer]; + intersectRect = CGRectIntersection(parentHitRect, intersectRectInParentCoordinates); + if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) { + clippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates + parentRect:parentHitRect rectEdge:clippedEdges]; + if (parentClipsToBounds) { + clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates + parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges]; + } + } + + // Advance up the tree. + intersectLayer = intersectSuperlayer; + intersectSuperlayer = intersectLayer.superlayer; + } + + CGRect finalRect = [intersectLayer convertRect:intersectRect toLayer:layer]; + UIColor *fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.4]; + + // determine if edges are clipped + if (clippedEdges == UIRectEdgeNone) { + _debugHighlightOverlay.backgroundColor = fillColor; + } else { + const CGFloat borderWidth = 2.0; + UIColor *borderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8]; + UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; + CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0); + UIGraphicsBeginImageContext(imgRect.size); + + [fillColor setFill]; + UIRectFill(imgRect); + + [self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect]; + [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect]; + + UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth); + _debugHighlightOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets + resizingMode:UIImageResizingModeStretch]; + _debugHighlightOverlay.backgroundColor = nil; + } + + _debugHighlightOverlay.frame = finalRect; + } +} + +- (UIRectEdge)setEdgesOfIntersectionForChildRect:(CGRect)childRect parentRect:(CGRect)parentRect rectEdge:(UIRectEdge)rectEdge +{ + if (childRect.origin.y < parentRect.origin.y) { + rectEdge |= UIRectEdgeTop; + } + if (childRect.origin.x < parentRect.origin.x) { + rectEdge |= UIRectEdgeLeft; + } + if (CGRectGetMaxY(childRect) > CGRectGetMaxY(parentRect)) { + rectEdge |= UIRectEdgeBottom; + } + if (CGRectGetMaxX(childRect) > CGRectGetMaxX(parentRect)) { + rectEdge |= UIRectEdgeRight; + } + + return rectEdge; +} + +- (void)drawEdgeIfClippedWithEdges:(UIRectEdge)rectEdge color:(UIColor *)color borderWidth:(CGFloat)borderWidth imgRect:(CGRect)imgRect +{ + [color setFill]; + + if (rectEdge & UIRectEdgeTop) { + UIRectFill(CGRectMake(0.0, 0.0, imgRect.size.width, borderWidth)); + } + if (rectEdge & UIRectEdgeLeft) { + UIRectFill(CGRectMake(0.0, 0.0, borderWidth, imgRect.size.height)); + } + if (rectEdge & UIRectEdgeBottom) { + UIRectFill(CGRectMake(0.0, imgRect.size.height - borderWidth, imgRect.size.width, borderWidth)); + } + if (rectEdge & UIRectEdgeRight) { + UIRectFill(CGRectMake(imgRect.size.width - borderWidth, 0.0, borderWidth, imgRect.size.height)); + } +} + ++ (void)setEnableHitTestDebug:(BOOL)enable +{ + _enableHitTestDebug = enable; +} + +>>>>>>> master +@end diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index aa7df08583..4bb108b66a 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -57,18 +57,50 @@ ASDISPLAYNODE_EXTERN_C_END - (void)didCompleteLayoutTransition:(id)context; /** - * @abstract Transitions the current layout with a new constrained size. + * @abstract Transitions the current layout with a new constrained size. Must be called on main thread. * - * @discussion Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. - * If the passed constrainedSize is the the same as the node's current constrained size, this method is noop. + * @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. + * + * @param shouldMeasureAsync Measure the layout asynchronously. + * + * @param measurementCompletion Optional completion block called only if a new layout is calculated. + * It is called on main, right after the measurement and before -animateLayoutTransition:. + * + * @discussion If the passed constrainedSize is the the same as the node's current constrained size, this method is noop. + * + * @see animateLayoutTransition: */ -- (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated; +- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize + animated:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(void(^)())completion; /** - * @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`. + * @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`. Must be called on main thread. * - * @discussion Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. + * @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. + * + * @param shouldMeasureAsync Measure the layout asynchronously. + * + * @param measurementCompletion Optional completion block called only if a new layout is calculated. + * It is called right after the measurement and before -animateLayoutTransition:. + * + * @see animateLayoutTransition: */ -- (ASLayout *)transitionLayoutWithAnimation:(BOOL)animated; +- (void)transitionLayoutWithAnimation:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(void(^)())completion; + + +/** + * @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network. + * Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished. + */ +- (BOOL)placeholderShouldPersist; + +/** + * @abstract Cancels all performing layout transitions. Can be called on any thread. + */ +- (void)cancelLayoutTransitionsInProgress; @end diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 699b6213ed..eb85fdffed 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -81,7 +81,7 @@ NS_ASSUME_NONNULL_BEGIN * * @discussion Subclasses override this method to layout all subnodes or subviews. */ -- (void)layout; +- (void)layout ASDISPLAYNODE_REQUIRES_SUPER; /** * @abstract Called on the main thread by the view's -layoutSubviews, after -layout. @@ -89,7 +89,7 @@ NS_ASSUME_NONNULL_BEGIN * @discussion Gives a chance for subclasses to perform actions after the subclass and superclass have finished laying * out. */ -- (void)layoutDidFinish; +- (void)layoutDidFinish ASDISPLAYNODE_REQUIRES_SUPER; /** * @abstract Called on a background thread if !isNodeLoaded - called on the main thread if isNodeLoaded. @@ -97,7 +97,7 @@ NS_ASSUME_NONNULL_BEGIN * @discussion When the .calculatedLayout property is set to a new ASLayout (directly from -calculateLayoutThatFits: or * calculated via use of -layoutSpecThatFits:), subclasses may inspect it here. */ -- (void)calculatedLayoutDidChange; +- (void)calculatedLayoutDidChange ASDISPLAYNODE_REQUIRES_SUPER; /** @name Layout calculation */ @@ -206,6 +206,8 @@ NS_ASSUME_NONNULL_BEGIN * * @discussion Subclasses may override this method to be notified when display (asynchronous or synchronous) is * about to begin. + * + * @note Called on the main thread only */ - (void)displayWillStart ASDISPLAYNODE_REQUIRES_SUPER; @@ -214,6 +216,8 @@ NS_ASSUME_NONNULL_BEGIN * * @discussion Subclasses may override this method to be notified when display (asynchronous or synchronous) has * completed. + * + * @note Called on the main thread only */ - (void)displayDidFinish ASDISPLAYNODE_REQUIRES_SUPER; @@ -225,9 +229,14 @@ NS_ASSUME_NONNULL_BEGIN * @discussion Subclasses may use this to monitor when they become visible, should free cached data, and much more. * @see ASInterfaceState */ -- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState; +- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState ASDISPLAYNODE_REQUIRES_SUPER; -- (void)visibilityDidChange:(BOOL)isVisible; +/** + * @abstract Called whenever the visiblity of the node changed. + * + * @discussion Subclasses may use this to monitor when they become visible. + */ +- (void)visibilityDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER; /** * Called just before the view is added to a window. @@ -340,7 +349,7 @@ NS_ASSUME_NONNULL_BEGIN * @param touches A set of UITouch instances. * @param event A UIEvent associated with the touch. */ -- (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event; +- (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; /** * @abstract Tells the node when touches moved in its view. @@ -348,7 +357,7 @@ NS_ASSUME_NONNULL_BEGIN * @param touches A set of UITouch instances. * @param event A UIEvent associated with the touch. */ -- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event; +- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; /** * @abstract Tells the node when touches ended in its view. @@ -356,7 +365,7 @@ NS_ASSUME_NONNULL_BEGIN * @param touches A set of UITouch instances. * @param event A UIEvent associated with the touch. */ -- (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event; +- (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; /** * @abstract Tells the node when touches was cancelled in its view. @@ -364,7 +373,7 @@ NS_ASSUME_NONNULL_BEGIN * @param touches A set of UITouch instances. * @param event A UIEvent associated with the touch. */ -- (void)touchesCancelled:(nullable NSSet *)touches withEvent:(nullable UIEvent *)event; +- (void)touchesCancelled:(nullable NSSet *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; /** @name Managing Gesture Recognizers */ diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 2f67dc170c..2ec93bdad8 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -177,7 +177,7 @@ NS_ASSUME_NONNULL_BEGIN * @warning The first access to it must be on the main thread, and should only be used on the main thread thereafter as * well. */ -@property (nonatomic, readonly, retain) UIView *view; +@property (nonatomic, readonly, strong) UIView *view; /** * @abstract Returns whether a node's backing view or layer is loaded. @@ -202,7 +202,7 @@ NS_ASSUME_NONNULL_BEGIN * @warning The first access to it must be on the main thread, and should only be used on the main thread thereafter as * well. */ -@property (nonatomic, readonly, retain) CALayer * _Nonnull layer; +@property (nonatomic, readonly, strong) CALayer * _Nonnull layer; /** * @abstract Returns the Interface State of the node. @@ -351,7 +351,7 @@ NS_ASSUME_NONNULL_BEGIN /** * @abstract The receiver's immediate subnodes. */ -@property (nonatomic, readonly, retain) NSArray *subnodes; +@property (nonatomic, readonly, copy) NSArray *subnodes; /** * @abstract The receiver's supernode. @@ -428,6 +428,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) BOOL displaySuspended; +/** + * @abstract Whether size changes should be animated. Default to YES. + */ +@property (nonatomic, assign) BOOL shouldAnimateSizeChanges; + /** * @abstract Prevent the node and its descendants' layer from displaying. * @@ -617,7 +622,7 @@ NS_ASSUME_NONNULL_END */ - (void)setNeedsLayout; -@property (atomic, retain, nullable) id contents; // default=nil +@property (atomic, strong, nullable) id contents; // default=nil @property (atomic, assign) BOOL clipsToBounds; // default==NO @property (atomic, getter=isOpaque) BOOL opaque; // default==YES @@ -645,9 +650,9 @@ NS_ASSUME_NONNULL_END * @discussion In contrast to UIView, setting a transparent color will not set opaque = NO. * This only affects nodes that implement +drawRect like ASTextNode. */ -@property (atomic, retain, nullable) UIColor *backgroundColor; // default=nil +@property (atomic, strong, nullable) UIColor *backgroundColor; // default=nil -@property (atomic, retain, null_resettable) UIColor *tintColor; // default=Blue +@property (atomic, strong, null_resettable) UIColor *tintColor; // default=Blue - (void)tintColorDidChange; // Notifies the node when the tintColor has changed. /** @@ -690,20 +695,30 @@ NS_ASSUME_NONNULL_END - (nullable UIView *)preferredFocusedView; #endif +@end + +@interface ASDisplayNode (UIViewBridgeAccessibility) + // Accessibility support -@property (atomic, assign) BOOL isAccessibilityElement; -@property (nullable, atomic, copy) NSString *accessibilityLabel; -@property (nullable, atomic, copy) NSString *accessibilityHint; -@property (nullable, atomic, copy) NSString *accessibilityValue; -@property (atomic, assign) UIAccessibilityTraits accessibilityTraits; -@property (atomic, assign) CGRect accessibilityFrame; -@property (nullable, atomic, retain) NSString *accessibilityLanguage; -@property (atomic, assign) BOOL accessibilityElementsHidden; -@property (atomic, assign) BOOL accessibilityViewIsModal; -@property (atomic, assign) BOOL shouldGroupAccessibilityChildren; +@property (nonatomic, assign) BOOL isAccessibilityElement; +@property (nonatomic, copy, nullable) NSString *accessibilityLabel; +@property (nonatomic, copy, nullable) NSString *accessibilityHint; +@property (nonatomic, copy, nullable) NSString *accessibilityValue; +@property (nonatomic, assign) UIAccessibilityTraits accessibilityTraits; +@property (nonatomic, assign) CGRect accessibilityFrame; +@property (nonatomic, copy, nullable) UIBezierPath *accessibilityPath; +@property (nonatomic, assign) CGPoint accessibilityActivationPoint; +@property (nonatomic, copy, nullable) NSString *accessibilityLanguage; +@property (nonatomic, assign) BOOL accessibilityElementsHidden; +@property (nonatomic, assign) BOOL accessibilityViewIsModal; +@property (nonatomic, assign) BOOL shouldGroupAccessibilityChildren; +@property (nonatomic, assign) UIAccessibilityNavigationStyle accessibilityNavigationStyle; +#if TARGET_OS_TV +@property(nonatomic, copy, nullable) NSArray *accessibilityHeaderElements; +#endif // Accessibility identification support -@property (nullable, nonatomic, copy) NSString *accessibilityIdentifier; +@property (nonatomic, copy, nullable) NSString *accessibilityIdentifier; @end diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 01af3356e0..2a9501e124 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -9,9 +9,10 @@ #import "ASDisplayNodeInternal.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+FrameworkPrivate.h" -#import "ASLayoutOptionsPrivate.h" +#import "ASDisplayNode+Beta.h" #import +#import #import "_ASAsyncTransaction.h" #import "_ASAsyncTransactionContainer+Private.h" @@ -19,9 +20,11 @@ #import "_ASDisplayView.h" #import "_ASScopeTimer.h" #import "_ASCoreAnimationExtras.h" +#import "ASDisplayNodeLayoutContext.h" #import "ASDisplayNodeExtras.h" #import "ASEqualityHelpers.h" -#import "NSArray+Diffing.h" +#import "ASRunLoopQueue.h" +#import "ASEnvironmentInternal.h" #import "ASInternalHelpers.h" #import "ASLayout.h" @@ -32,7 +35,7 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp"; -@interface ASDisplayNode () +@interface ASDisplayNode () /** * @@ -57,12 +60,13 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS @implementation ASDisplayNode // these dynamic properties all defined in ASLayoutOptionsPrivate.m -@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition, layoutOptions; +@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition; @synthesize name = _name; @synthesize preferredFrameSize = _preferredFrameSize; @synthesize isFinalLayoutable = _isFinalLayoutable; +@synthesize threadSafeBounds = _threadSafeBounds; -static BOOL usesImplicitHierarchyManagement = FALSE; +static BOOL usesImplicitHierarchyManagement = NO; + (BOOL)usesImplicitHierarchyManagement { @@ -91,7 +95,7 @@ _ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node) } /** - * Returns ASDisplayNodeFlags for the givern class/instance. instance MAY BE NIL. + * Returns ASDisplayNodeFlags for the given class/instance. instance MAY BE NIL. * * @param c the class, required * @param instance the instance, which may be nil. (If so, the class is inspected instead) @@ -110,6 +114,7 @@ static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *i flags.isInHierarchy = NO; flags.displaysAsynchronously = YES; + flags.shouldAnimateSizeChanges = YES; flags.implementsDrawRect = ([c respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0); flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0); if (instance) { @@ -127,7 +132,7 @@ static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *i /** * Returns ASDisplayNodeMethodOverrides for the given class * - * @param c the class, requireed. + * @param c the class, required. * * @return ASDisplayNodeMethodOverrides. */ @@ -193,6 +198,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@"); } ++ (void)load +{ + // Ensure this value is cached on the main thread before needed in the background. + ASScreenScale(); +} + + (BOOL)layerBackedNodesEnabled { return YES; @@ -210,49 +221,22 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) + (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node { - static ASDN::RecursiveMutex __displaySchedulerLock; - static NSMutableArray *__nodesToDisplay = nil; - static BOOL __displayScheduled = NO; - - BOOL scheduleDisplayPassNow = NO; - { - ASDN::MutexLocker l(__displaySchedulerLock); - - if (!__nodesToDisplay) { - __nodesToDisplay = [NSMutableArray array]; - } - - if ([__nodesToDisplay indexOfObjectIdenticalTo:node] == NSNotFound) { - [__nodesToDisplay addObject:node]; - } - - if (!__displayScheduled) { - scheduleDisplayPassNow = YES; - __displayScheduled = YES; - } - } - - if (scheduleDisplayPassNow) { - // It's essenital that any layout pass that is scheduled during the current - // runloop has a chance to be applied / scheduled, so always perform this after the current runloop. - dispatch_async(dispatch_get_main_queue(), ^{ - NSArray *displayingNodes = nil; - // Create a lock scope. Snatch the waiting nodes, let the next batch create a new container. - { - ASDN::MutexLocker l(__displaySchedulerLock); - displayingNodes = [__nodesToDisplay copy]; - __nodesToDisplay = nil; - __displayScheduled = NO; + static dispatch_once_t onceToken; + static ASRunLoopQueue *renderQueue; + dispatch_once(&onceToken, ^{ + renderQueue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() + andHandler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) { + CFAbsoluteTime timestamp = isQueueDrained ? CFAbsoluteTimeGetCurrent() : 0; + [dequeuedItem _recursivelyTriggerDisplayAndBlock:NO]; + if (isQueueDrained) { + [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification + object:nil + userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}]; } - CFAbsoluteTime timestamp = CFAbsoluteTimeGetCurrent(); - for (ASDisplayNode *node in displayingNodes) { - [node __recursivelyTriggerDisplayAndBlock:NO]; - } - [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification - object:displayingNodes - userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: [NSNumber numberWithDouble:timestamp]}]; - }); - } + }]; + }); + + [renderQueue enqueue:node]; } #pragma mark - Lifecycle @@ -268,9 +252,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _contentsScaleForDisplay = ASScreenScale(); _displaySentinel = [[ASSentinel alloc] init]; _preferredFrameSize = CGSizeZero; + + _environmentState = ASEnvironmentStateMakeDefault(); } -- (id)init +- (instancetype)init { if (!(self = [super init])) return nil; @@ -280,7 +266,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return self; } -- (id)initWithViewClass:(Class)viewClass +- (instancetype)initWithViewClass:(Class)viewClass { if (!(self = [super init])) return nil; @@ -294,7 +280,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return self; } -- (id)initWithLayerClass:(Class)layerClass +- (instancetype)initWithLayerClass:(Class)layerClass { if (!(self = [super init])) return nil; @@ -309,12 +295,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return self; } -- (id)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock +- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock { return [self initWithViewBlock:viewBlock didLoadBlock:nil]; } -- (id)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock +- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock { if (!(self = [super init])) return nil; @@ -329,12 +315,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return self; } -- (id)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock +- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock { return [self initWithLayerBlock:layerBlock didLoadBlock:nil]; } -- (id)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock +- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock { if (!(self = [super init])) return nil; @@ -353,7 +339,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)dealloc { ASDisplayNodeAssertMainThread(); - + // Synchronous nodes may not be able to call the hierarchy notifications, so only enforce for regular nodes. + ASDisplayNodeAssert(_flags.synchronous || !ASInterfaceStateIncludesVisible(_interfaceState), @"Node should always be marked invisible before deallocating; interfaceState: %lu, %@", (unsigned long)_interfaceState, self); + self.asyncLayer.asyncDelegate = nil; _view.asyncdisplaykit_node = nil; _layer.asyncdisplaykit_node = nil; @@ -374,9 +362,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self __setSupernode:nil]; _pendingViewState = nil; - _replaceAsyncSentinel = nil; _displaySentinel = nil; + _transitionSentinel = nil; _pendingDisplayNodes = nil; } @@ -442,6 +430,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { CALayer *layer; ASDN::MutexLocker l(_propertyLock); + ASDisplayNodeAssert(_flags.layerBacked, @"_layerToLoad is only for layer-backed nodes"); if (_layerBlock) { layer = _layerBlock(); @@ -497,10 +486,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) TIME_SCOPED(_debugTimeForDidLoad); [self __didLoad]; } - - if (self.placeholderEnabled) { - [self _setupPlaceholderLayer]; - } } - (UIView *)view @@ -607,171 +592,211 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { - return [self measureWithSizeRange:constrainedSize completion:^{ + ASDN::MutexLocker l(_propertyLock); + if (! [self shouldMeasureWithSizeRange:constrainedSize]) { + return _layout; + } + + [self cancelLayoutTransitionsInProgress]; + + ASLayout *previousLayout = _layout; + ASSizeRange previousConstrainedSize = _constrainedSize; + ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize]; + + if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { + _pendingLayoutContext = [[ASDisplayNodeLayoutContext alloc] initWithNode:self + pendingLayout:newLayout + pendingConstrainedSize:constrainedSize + previousLayout:previousLayout + previousConstrainedSize:previousConstrainedSize]; + } else { + ASDisplayNodeLayoutContext *layoutContext; if (self.usesImplicitHierarchyManagement) { - [self __implicitlyInsertSubnodes]; - [self __implicitlyRemoveSubnodes]; + layoutContext = [[ASDisplayNodeLayoutContext alloc] initWithNode:self + pendingLayout:newLayout + pendingConstrainedSize:constrainedSize + previousLayout:previousLayout + previousConstrainedSize:previousConstrainedSize]; } - [self __completeLayoutCalculation]; - }]; + [self applyLayout:newLayout constrainedSize:constrainedSize layoutContext:layoutContext]; + [self _completeLayoutCalculation]; + } + + return newLayout; } -- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize completion:(void(^)())completion +- (BOOL)shouldMeasureWithSizeRange:(ASSizeRange)constrainedSize { ASDN::MutexLocker l(_propertyLock); - if (![self __shouldSize]) - return nil; + if (![self __shouldSize]) { + return NO; + } + + if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { + ASLayoutableContext context = ASLayoutableGetCurrentContext(); + if (ASLayoutableContextIsNull(context) || _pendingTransitionID != context.transitionID) { + return NO; + } + } // only calculate the size if // - we haven't already // - the constrained size range is different - if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) { - _previousLayout = _layout; - _layout = [self calculateLayoutThatFits:constrainedSize]; - - ASDisplayNodeAssertTrue(_layout.layoutableObject == self); - ASDisplayNodeAssertTrue(_layout.size.width >= 0.0); - ASDisplayNodeAssertTrue(_layout.size.height >= 0.0); - - _previousConstrainedSize = _constrainedSize; - _constrainedSize = constrainedSize; - - if (self.usesImplicitHierarchyManagement) { - [self __calculateSubnodeOperations]; - } - _flags.isMeasured = YES; - - completion(); - } - - return _layout; + return (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)); } -- (ASLayout *)transitionLayoutWithAnimation:(BOOL)animated +- (void)transitionLayoutWithAnimation:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(void(^)())completion { + ASSizeRange currentConstrainedSize = _constrainedSize; [self invalidateCalculatedLayout]; - return [self transitionLayoutWithSizeRange:_constrainedSize animated:animated]; + [self transitionLayoutWithSizeRange:currentConstrainedSize + animated:animated + shouldMeasureAsync:shouldMeasureAsync + measurementCompletion:completion]; } -- (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated +- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize + animated:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(void(^)())completion { - _usesImplicitHierarchyManagement = YES; // Temporary flag for 1.9.x - return [self measureWithSizeRange:constrainedSize completion:^{ - _usesImplicitHierarchyManagement = NO; // Temporary flag for 1.9.x - _transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated delegate:self]; - [self __implicitlyInsertSubnodes]; - [self animateLayoutTransition:_transitionContext]; - }]; -} + ASDisplayNodeAssertMainThread(); + if (! [self shouldMeasureWithSizeRange:constrainedSize]) { + return; + } + + { + ASDN::MutexLocker l(_propertyLock); + ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one."); + } -- (void)__calculateSubnodeOperations -{ - if (_previousLayout) { - NSIndexSet *insertions, *deletions; - [_previousLayout.immediateSublayouts asdk_diffWithArray:_layout.immediateSublayouts - insertions:&insertions - deletions:&deletions - compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { - return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); - }]; - filterNodesInLayoutAtIndexes(_layout, insertions, &_insertedSubnodes, &_insertedSubnodePositions); - filterNodesInLayoutAtIndexesWithIntersectingNodes(_previousLayout, - deletions, - _insertedSubnodes, - &_removedSubnodes, - &_removedSubnodePositions); + int32_t transitionID = [self _newTransitionID]; + + ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) { + ASDisplayNodeAssert([node _hasTransitionsInProgress] == NO, @"Can't start a transition when one of the subnodes is performing one."); + node.hierarchyState |= ASHierarchyStateLayoutPending; + node.pendingTransitionID = transitionID; + }); + + void (^transitionBlock)() = ^{ + if ([self _shouldAbortTransitionWithID:transitionID]) { + return; + } + + ASLayout *newLayout; + { + ASLayoutableSetCurrentContext(ASLayoutableContextMake(transitionID, NO)); + + ASDN::MutexLocker l(_propertyLock); + BOOL disableImplicitHierarchyManagement = self.usesImplicitHierarchyManagement == NO; + self.usesImplicitHierarchyManagement = YES; // Temporary flag for 1.9.x + newLayout = [self calculateLayoutThatFits:constrainedSize]; + if (disableImplicitHierarchyManagement) { + self.usesImplicitHierarchyManagement = NO; // Temporary flag for 1.9.x + } + + ASLayoutableClearCurrentContext(); + } + + if ([self _shouldAbortTransitionWithID:transitionID]) { + return; + } + + ASPerformBlockOnMainThread(^{ + // Grab _propertyLock here to make sure this transition isn't invalidated + // right after it passed the validation test and before it proceeds + ASDN::MutexLocker l(_propertyLock); + + if ([self _shouldAbortTransitionWithID:transitionID]) { + return; + } + + ASLayout *previousLayout = _layout; + ASSizeRange previousConstrainedSize = _constrainedSize; + [self applyLayout:newLayout constrainedSize:constrainedSize layoutContext:nil]; + + [self _invalidateTransitionSentinel]; + + ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) { + [node applyPendingLayoutContext]; + [node _completeLayoutCalculation]; + node.hierarchyState &= (~ASHierarchyStateLayoutPending); + }); + + if (completion) { + completion(); + } + + _pendingLayoutContext = [[ASDisplayNodeLayoutContext alloc] initWithNode:self + pendingLayout:newLayout + pendingConstrainedSize:constrainedSize + previousLayout:previousLayout + previousConstrainedSize:previousConstrainedSize]; + [_pendingLayoutContext applySubnodeInsertions]; + + _transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated + layoutDelegate:_pendingLayoutContext + completionDelegate:self]; + [self animateLayoutTransition:_transitionContext]; + }); + }; + + if (shouldMeasureAsync) { + ASPerformBlockOnBackgroundThread(transitionBlock); } else { - NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_layout.immediateSublayouts count])]; - filterNodesInLayoutAtIndexes(_layout, indexes, &_insertedSubnodes, &_insertedSubnodePositions); - _removedSubnodes = nil; + transitionBlock(); } } -- (void)__completeLayoutCalculation +- (void)_completeLayoutCalculation { - _insertedSubnodes = nil; - _removedSubnodes = nil; - _previousLayout = nil; + ASDN::MutexLocker l(_propertyLock); [self calculatedLayoutDidChange]; - // 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 - [self __initPlaceholder]; -} + // We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go. + // This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously. + // First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync. + if (_placeholderEnabled && [self _displaysAsynchronously] && self.contents == nil) { + + // Zero-sized nodes do not require a placeholder. + CGSize layoutSize = (_layout ? _layout.size : CGSizeZero); + if (CGSizeEqualToSize(layoutSize, CGSizeZero)) { + return; + } -- (void)__initPlaceholder -{ - if (self.placeholderEnabled && [self _displaysAsynchronously] && - _layout.size.width > 0.0 && _layout.size.height > 0.0) { if (!_placeholderImage) { _placeholderImage = [self placeholderImage]; } - - if (_placeholderLayer) { - [self _setupPlaceholderLayerContents]; - } } } -/** - * @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector. - */ -static inline void filterNodesInLayoutAtIndexes( - ASLayout *layout, - NSIndexSet *indexes, - NSArray * __strong *storedNodes, - std::vector *storedPositions - ) -{ - filterNodesInLayoutAtIndexesWithIntersectingNodes(layout, indexes, nil, storedNodes, storedPositions); -} - -/** - * @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector. - * @discussion If the node exists in the `intersectingNodes` array, the node is not added to `storedNodes`. - */ -static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( - ASLayout *layout, - NSIndexSet *indexes, - NSArray *intersectingNodes, - NSArray * __strong *storedNodes, - std::vector *storedPositions - ) -{ - NSMutableArray *nodes = [NSMutableArray array]; - std::vector positions = std::vector(); - NSInteger idx = [indexes firstIndex]; - while (idx != NSNotFound) { - BOOL skip = NO; - ASDisplayNode *node = (ASDisplayNode *)layout.immediateSublayouts[idx].layoutableObject; - ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts"); - for (ASDisplayNode *i in intersectingNodes) { - if (node == i) { - skip = YES; - break; - } - } - if (!skip) { - [nodes addObject:node]; - positions.push_back(idx); - } - idx = [indexes indexGreaterThanIndex:idx]; - } - *storedNodes = nodes; - *storedPositions = positions; -} - (void)calculatedLayoutDidChange { // subclass override } +- (void)cancelLayoutTransitionsInProgress +{ + ASDN::MutexLocker l(_propertyLock); + if ([self _hasTransitionsInProgress]) { + // Invalidate transition sentinel to cancel transitions in progress + [self _invalidateTransitionSentinel]; + // Tell subnodes to exit layout pending state and clear related properties + ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) { + node.hierarchyState &= (~ASHierarchyStateLayoutPending); + }); + } +} + #pragma mark - Layout Transition - (BOOL)usesImplicitHierarchyManagement { ASDN::MutexLocker l(_propertyLock); - return _usesImplicitHierarchyManagement ?: [[self class] usesImplicitHierarchyManagement]; + return _usesImplicitHierarchyManagement ? : [[self class] usesImplicitHierarchyManagement]; } - (void)setUsesImplicitHierarchyManagement:(BOOL)value @@ -786,70 +811,18 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( [context completeTransition:YES]; } -- (void)didCompleteTransitionLayout:(id)context +- (void)didCompleteLayoutTransition:(id)context { - [self __implicitlyRemoveSubnodes]; - [self __completeLayoutCalculation]; + [_pendingLayoutContext applySubnodeRemovals]; + [self _completeLayoutCalculation]; + _pendingLayoutContext = nil; } -#pragma mark - Implicit node hierarchy managagment - -- (void)__implicitlyInsertSubnodes -{ - for (NSInteger i = 0; i < [_insertedSubnodes count]; i++) { - NSInteger p = _insertedSubnodePositions[i]; - [self insertSubnode:_insertedSubnodes[i] atIndex:p]; - } -} - -- (void)__implicitlyRemoveSubnodes -{ - for (NSInteger i = 0; i < [_removedSubnodes count]; i++) { - [_removedSubnodes[i] removeFromSupernode]; - } -} - -#pragma mark - _ASTransitionContextDelegate - -- (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context -{ - return _subnodes; -} - -- (NSArray *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context -{ - return _insertedSubnodes; -} - -- (NSArray *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context -{ - return _removedSubnodes; -} - -- (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key -{ - if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { - return _previousLayout; - } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { - return _layout; - } else { - return nil; - } -} -- (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key -{ - if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { - return _previousConstrainedSize; - } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { - return _constrainedSize; - } else { - return ASSizeRangeMake(CGSizeZero, CGSizeZero); - } -} +#pragma mark - _ASTransitionContextCompletionDelegate - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete { - [self didCompleteTransitionLayout:context]; + [self didCompleteLayoutTransition:context]; _transitionContext = nil; } @@ -868,11 +841,7 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( - (BOOL)_displaysAsynchronously { ASDisplayNodeAssertThreadAffinity(self); - if (self.isSynchronous) { - return NO; - } else { - return _flags.displaysAsynchronously; - } + return _flags.synchronous == NO && _flags.displaysAsynchronously; } - (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously @@ -1063,13 +1032,14 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( { ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(_propertyLock); - if (CGRectEqualToRect(self.bounds, CGRectZero)) { + CGRect bounds = self.bounds; + if (CGRectEqualToRect(bounds, CGRectZero)) { // 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. return; } - _placeholderLayer.frame = self.bounds; + _placeholderLayer.frame = bounds; [self layout]; [self layoutDidFinish]; } @@ -1117,7 +1087,7 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo { ASDisplayNodeAssertThreadAffinity(self); // Get root node of the accessible node hierarchy, if node not specified - node = node ? node : ASDisplayNodeUltimateParentOfNode(self); + node = node ? : ASDisplayNodeUltimateParentOfNode(self); // Calculate transform to map points between coordinate spaces CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); @@ -1132,7 +1102,7 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo { ASDisplayNodeAssertThreadAffinity(self); // Get root node of the accessible node hierarchy, if node not specified - node = node ? node : ASDisplayNodeUltimateParentOfNode(self); + node = node ? : ASDisplayNodeUltimateParentOfNode(self); // Calculate transform to map points between coordinate spaces CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); @@ -1147,7 +1117,7 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo { ASDisplayNodeAssertThreadAffinity(self); // Get root node of the accessible node hierarchy, if node not specified - node = node ? node : ASDisplayNodeUltimateParentOfNode(self); + node = node ? : ASDisplayNodeUltimateParentOfNode(self); // Calculate transform to map points between coordinate spaces CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); @@ -1162,7 +1132,7 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo { ASDisplayNodeAssertThreadAffinity(self); // Get root node of the accessible node hierarchy, if node not specified - node = node ? node : ASDisplayNodeUltimateParentOfNode(self); + node = node ? : ASDisplayNodeUltimateParentOfNode(self); // Calculate transform to map points between coordinate spaces CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); @@ -1584,13 +1554,14 @@ static NSInteger incrementIfFound(NSInteger i) { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"Should not cause recursive __enterHierarchy"); - // Profiling has shown that locking this method is benificial, so each of the property accesses don't have to lock and unlock. + // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. ASDN::MutexLocker l(_propertyLock); - if (!self.inHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { - self.inHierarchy = YES; + if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { _flags.isEnteringHierarchy = YES; - if (self.shouldRasterizeDescendants) { + _flags.isInHierarchy = YES; + + if (_flags.shouldRasterizeDescendants) { // Nodes that are descendants of a rasterized container do not have views or layers, and so cannot receive visibility notifications directly via orderIn/orderOut CALayer actions. Manually send visibility notifications to rasterized descendants. [self _recursiveWillEnterHierarchy]; } else { @@ -1598,9 +1569,20 @@ static NSInteger incrementIfFound(NSInteger i) { } _flags.isEnteringHierarchy = NO; - CALayer *layer = self.layer; - if (!layer.contents) { + + // If we don't have contents finished drawing by the time we are on screen, immediately add the placeholder (if it is enabled and we do have something to draw). + if (self.contents == nil) { + CALayer *layer = self.layer; [layer setNeedsDisplay]; + + if ([self _shouldHavePlaceholderLayer]) { + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + [self _setupPlaceholderLayerIfNeeded]; + _placeholderLayer.opacity = 1.0; + [CATransaction commit]; + [layer addSublayer:_placeholderLayer]; + } } } } @@ -1610,21 +1592,22 @@ static NSInteger incrementIfFound(NSInteger i) { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"Should not cause recursive __exitHierarchy"); - // Profiling has shown that locking this method is benificial, so each of the property accesses don't have to lock and unlock. + // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. ASDN::MutexLocker l(_propertyLock); - if (self.inHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { - self.inHierarchy = NO; + if (_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { + _flags.isExitingHierarchy = YES; + _flags.isInHierarchy = NO; [self.asyncLayer cancelAsyncDisplay]; - _flags.isExitingHierarchy = YES; - if (self.shouldRasterizeDescendants) { + if (_flags.shouldRasterizeDescendants) { // Nodes that are descendants of a rasterized container do not have views or layers, and so cannot receive visibility notifications directly via orderIn/orderOut CALayer actions. Manually send visibility notifications to rasterized descendants. [self _recursiveDidExitHierarchy]; } else { [self didExitHierarchy]; } + _flags.isExitingHierarchy = NO; } } @@ -1713,7 +1696,7 @@ static NSInteger incrementIfFound(NSInteger i) { // The node sending the message should usually be passed as the parameter, similar to the delegation pattern. - (void)_pendingNodeWillDisplay:(ASDisplayNode *)node { - ASDN::MutexLocker l(_propertyLock); + ASDisplayNodeAssertMainThread(); if (!_pendingDisplayNodes) { _pendingDisplayNodes = [[NSMutableSet alloc] init]; @@ -1726,57 +1709,67 @@ static NSInteger incrementIfFound(NSInteger i) { // The node sending the message should usually be passed as the parameter, similar to the delegation pattern. - (void)_pendingNodeDidDisplay:(ASDisplayNode *)node { - ASDN::MutexLocker l(_propertyLock); + ASDisplayNodeAssertMainThread(); [_pendingDisplayNodes removeObject:node]; - // only trampoline if there is a placeholder and nodes are done displaying - if ([self _pendingDisplayNodesHaveFinished] && _placeholderLayer.superlayer) { - dispatch_async(dispatch_get_main_queue(), ^{ - void (^cleanupBlock)() = ^{ - [self _tearDownPlaceholderLayer]; - }; + if (_pendingDisplayNodes.count == 0 && _placeholderLayer.superlayer && ![self placeholderShouldPersist]) { + void (^cleanupBlock)() = ^{ + [_placeholderLayer removeFromSuperlayer]; + }; - if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) { - [CATransaction begin]; - [CATransaction setCompletionBlock:cleanupBlock]; - [CATransaction setAnimationDuration:_placeholderFadeDuration]; - _placeholderLayer.opacity = 0.0; - [CATransaction commit]; - } else { - cleanupBlock(); - } - }); + if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) { + [CATransaction begin]; + [CATransaction setCompletionBlock:cleanupBlock]; + [CATransaction setAnimationDuration:_placeholderFadeDuration]; + _placeholderLayer.opacity = 0.0; + [CATransaction commit]; + } else { + cleanupBlock(); + } } } -// Helper method to check that all nodes that the current node is waiting to display are finished -// Use this method to check to remove any placeholder layers -- (BOOL)_pendingDisplayNodesHaveFinished -{ - return _pendingDisplayNodes.count == 0; -} - // Helper method to summarize whether or not the node run through the display process - (BOOL)__implementsDisplay { - return _flags.implementsDrawRect == YES || _flags.implementsImageDisplay == YES || self.shouldRasterizeDescendants || _flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay; + return _flags.implementsDrawRect || _flags.implementsImageDisplay || _flags.shouldRasterizeDescendants || _flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay; } -- (void)_setupPlaceholderLayer +- (BOOL)placeholderShouldPersist +{ + return NO; +} + +- (BOOL)_shouldHavePlaceholderLayer +{ + return (_placeholderEnabled && [self __implementsDisplay]); +} + +- (void)_setupPlaceholderLayerIfNeeded { ASDisplayNodeAssertMainThread(); - _placeholderLayer = [CALayer layer]; - // do not set to CGFLOAT_MAX in the case that something needs to be overtop the placeholder - _placeholderLayer.zPosition = 9999.0; -} + if (!_placeholderLayer) { + _placeholderLayer = [CALayer layer]; + // do not set to CGFLOAT_MAX in the case that something needs to be overtop the placeholder + _placeholderLayer.zPosition = 9999.0; + } -- (void)_tearDownPlaceholderLayer -{ - ASDisplayNodeAssertMainThread(); - - [_placeholderLayer removeFromSuperlayer]; + if (_placeholderLayer.contents == nil) { + if (!_placeholderImage) { + _placeholderImage = [self placeholderImage]; + } + if (_placeholderImage) { + BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(_placeholderImage.capInsets, UIEdgeInsetsZero); + if (stretchable) { + ASDisplayNodeSetupLayerContentsWithResizableImage(_placeholderLayer, _placeholderImage); + } else { + _placeholderLayer.contentsScale = self.contentsScale; + _placeholderLayer.contents = (id)_placeholderImage.CGImage; + } + } + } } void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) @@ -1790,7 +1783,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) // (even a runloop observer at a late call order will not stop the next frame from compositing, showing placeholders). ASDisplayNode *node = [layer asyncdisplaykit_node]; - if (!layer.contents && [node __implementsDisplay]) { + if ([node __implementsDisplay]) { // For layers that do get displayed here, this immediately kicks off the work on the concurrent -[_ASDisplayLayer displayQueue]. // At the same time, it creates an associated _ASAsyncTransaction, which we can use to block on display completion. See ASDisplayNode+AsyncDisplay.mm. [layer displayIfNeeded]; @@ -1815,7 +1808,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) } } -- (void)__recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock +- (void)_recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock { ASDisplayNodeAssertMainThread(); @@ -1831,7 +1824,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously { - [self __recursivelyTriggerDisplayAndBlock:synchronously]; + [self _recursivelyTriggerDisplayAndBlock:synchronously]; } - (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay @@ -1851,6 +1844,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) ASDN::MutexLocker l(_propertyLock); if (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) { ASLayoutSpec *layoutSpec = [self layoutSpecThatFits:constrainedSize]; + layoutSpec.parent = self; // This causes upward propogation of any non-default layoutable values. layoutSpec.isMutable = NO; ASLayout *layout = [layoutSpec measureWithSizeRange:constrainedSize]; // Make sure layoutableObject of the root layout is `self`, so that the flattened layout will be structurally correct. @@ -1903,11 +1897,19 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) return _constrainedSize; } +- (void)setPendingTransitionID:(int32_t)pendingTransitionID +{ + ASDN::MutexLocker l(_propertyLock); + ASDisplayNodeAssertTrue(_pendingTransitionID < pendingTransitionID); + _pendingTransitionID = pendingTransitionID; +} + - (void)setPreferredFrameSize:(CGSize)preferredFrameSize { ASDN::MutexLocker l(_propertyLock); if (! CGSizeEqualToSize(_preferredFrameSize, preferredFrameSize)) { _preferredFrameSize = preferredFrameSize; + self.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(_preferredFrameSize), ASRelativeSizeMakeWithCGSize(_preferredFrameSize)); [self invalidateCalculatedLayout]; } } @@ -1918,6 +1920,18 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) return _preferredFrameSize; } +- (CGRect)threadSafeBounds +{ + ASDN::MutexLocker l(_propertyLock); + return _threadSafeBounds; +} + +- (void)setThreadSafeBounds:(CGRect)newBounds +{ + ASDN::MutexLocker l(_propertyLock); + _threadSafeBounds = newBounds; +} + - (UIImage *)placeholderImage { return nil; @@ -1964,6 +1978,23 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) if (![self supportsRangeManagedInterfaceState]) { self.interfaceState = ASInterfaceStateNone; + } else { + // This case is important when tearing down hierarchies. We must deliver a visibilityDidChange:NO callback, as part our API guarantee that this method can be used for + // things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail. + // Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the + // same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed). + // TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer + // integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call. + + if (ASInterfaceStateIncludesVisible(_interfaceState)) { + dispatch_async(dispatch_get_main_queue(), ^{ + // This block intentionally retains self. + ASDN::MutexLocker l(_propertyLock); + if (!_flags.isInHierarchy && ASInterfaceStateIncludesVisible(_interfaceState)) { + self.interfaceState = (_interfaceState & ~ASInterfaceStateVisible); + } + }); + } } } @@ -1975,13 +2006,11 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) _placeholderImage = nil; } -// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or exitInterfaceState: - (void)recursivelyClearContents { - for (ASDisplayNode *subnode in self.subnodes) { - [subnode recursivelyClearContents]; - } - [self clearContents]; + ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode * _Nonnull node) { + [node clearContents]; + }); } - (void)fetchData @@ -1996,13 +2025,11 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) } } -// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or enterInterfaceState: - (void)recursivelyFetchData { - for (ASDisplayNode *subnode in self.subnodes) { - [subnode recursivelyFetchData]; - } - [self fetchData]; + ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode * _Nonnull node) { + [node fetchData]; + }); } - (void)clearFetchedData @@ -2010,17 +2037,16 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) // subclass override } -// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or exitInterfaceState: - (void)recursivelyClearFetchedData { - for (ASDisplayNode *subnode in self.subnodes) { - [subnode recursivelyClearFetchedData]; - } - [self clearFetchedData]; + ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode * _Nonnull node) { + [node clearFetchedData]; + }); } - (void)visibilityDidChange:(BOOL)isVisible { + // subclass override } /** @@ -2029,7 +2055,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) */ - (BOOL)supportsRangeManagedInterfaceState { - return (_hierarchyState & ASHierarchyStateRangeManaged); + return ASHierarchyStateIncludesRangeManaged(_hierarchyState); } - (ASInterfaceState)interfaceState @@ -2040,6 +2066,8 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)setInterfaceState:(ASInterfaceState)newState { + // It should never be possible for a node to be visible but not be allowed / expected to display. + ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState)); ASInterfaceState oldState = ASInterfaceStateNone; { ASDN::MutexLocker l(_propertyLock); @@ -2083,7 +2111,13 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [self setDisplaySuspended:NO]; } else { [self setDisplaySuspended:YES]; - [self clearContents]; + //schedule clear contents on next runloop + dispatch_async(dispatch_get_main_queue(), ^{ + ASDN::MutexLocker l(_propertyLock); + if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { + [self clearContents]; + } + }); } } else { // NOTE: This case isn't currently supported as setInterfaceState: isn't exposed externally, and all @@ -2095,7 +2129,13 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; } else { [[self asyncLayer] cancelAsyncDisplay]; - [self clearContents]; + //schedule clear contents on next runloop + dispatch_async(dispatch_get_main_queue(), ^{ + ASDN::MutexLocker l(_propertyLock); + if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { + [self clearContents]; + } + }); } } } @@ -2108,11 +2148,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState); if (nowVisible != wasVisible) { - if (nowVisible) { - [self visibilityDidChange:YES]; - } else { - [self visibilityDidChange:NO]; - } + [self visibilityDidChange:nowVisible]; } [self interfaceStateDidChange:newState fromState:oldState]; @@ -2194,6 +2230,19 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) } } + if ((newState & ASHierarchyStateLayoutPending) != (oldState & ASHierarchyStateLayoutPending)) { + if (newState & ASHierarchyStateLayoutPending) { + // Entering layout pending state + } else { + // Leaving layout pending state, reset related properties + { + ASDN::MutexLocker l(_propertyLock); + _pendingTransitionID = ASLayoutableContextInvalidTransitionID; + _pendingLayoutContext = nil; + } + } + } + if (newState != oldState) { LOG(@"setHierarchyState: oldState = %lu, newState = %lu", (unsigned long)oldState, (unsigned long)newState); } @@ -2219,6 +2268,37 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) }); } +- (void)applyPendingLayoutContext +{ + ASDN::MutexLocker l(_propertyLock); + if (_pendingLayoutContext) { + [self applyLayout:_pendingLayoutContext.pendingLayout + constrainedSize:_pendingLayoutContext.pendingConstrainedSize + layoutContext:_pendingLayoutContext]; + _pendingLayoutContext = nil; + } +} + +- (void)applyLayout:(ASLayout *)layout + constrainedSize:(ASSizeRange)constrainedSize + layoutContext:(ASDisplayNodeLayoutContext *)layoutContext +{ + ASDN::MutexLocker l(_propertyLock); + _layout = layout; + + ASDisplayNodeAssertTrue(layout.layoutableObject == self); + ASDisplayNodeAssertTrue(layout.size.width >= 0.0); + ASDisplayNodeAssertTrue(layout.size.height >= 0.0); + + _constrainedSize = constrainedSize; + _flags.isMeasured = YES; + + if (self.usesImplicitHierarchyManagement && layoutContext != nil) { + [layoutContext applySubnodeInsertions]; + [layoutContext applySubnodeRemovals]; + } +} + - (void)layout { ASDisplayNodeAssertMainThread(); @@ -2239,34 +2319,18 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)displayWillStart { + ASDisplayNodeAssertMainThread(); + // in case current node takes longer to display than it's subnodes, treat it as a dependent node [self _pendingNodeWillDisplay:self]; [_supernode subnodeDisplayWillStart:self]; - - if (_placeholderImage && _placeholderLayer && self.layer.contents == nil) { - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - [self _setupPlaceholderLayerContents]; - _placeholderLayer.opacity = 1.0; - [CATransaction commit]; - [self.layer addSublayer:_placeholderLayer]; - } -} - -- (void)_setupPlaceholderLayerContents -{ - BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(_placeholderImage.capInsets, UIEdgeInsetsZero); - if (stretchable) { - ASDisplayNodeSetupLayerContentsWithResizableImage(_placeholderLayer, _placeholderImage); - } else { - _placeholderLayer.contentsScale = self.contentsScale; - _placeholderLayer.contents = (id)_placeholderImage.CGImage; - } } - (void)displayDidFinish { + ASDisplayNodeAssertMainThread(); + [self _pendingNodeDidDisplay:self]; [_supernode subnodeDisplayDidFinish:self]; @@ -2473,14 +2537,31 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, self.asyncLayer.displaySuspended = flag; if ([self __implementsDisplay]) { - if (flag) { - [_supernode subnodeDisplayDidFinish:self]; - } else { - [_supernode subnodeDisplayWillStart:self]; - } + // Display start and finish methods needs to happen on the main thread + ASPerformBlockOnMainThread(^{ + if (flag) { + [_supernode subnodeDisplayDidFinish:self]; + } else { + [_supernode subnodeDisplayWillStart:self]; + } + }); } } +- (BOOL)shouldAnimateSizeChanges +{ + ASDisplayNodeAssertThreadAffinity(self); + ASDN::MutexLocker l(_propertyLock); + return _flags.shouldAnimateSizeChanges; +} + +-(void)setShouldAnimateSizeChanges:(BOOL)shouldAnimateSizeChanges +{ + ASDisplayNodeAssertThreadAffinity(self); + ASDN::MutexLocker l(_propertyLock); + _flags.shouldAnimateSizeChanges = shouldAnimateSizeChanges; +} + static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; - (void)setDrawingPriority:(NSInteger)drawingPriority @@ -2492,7 +2573,7 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; objc_setAssociatedObject(self, ASDisplayNodeDrawingPriorityKey, nil, OBJC_ASSOCIATION_ASSIGN); } else { _flags.hasCustomDrawingPriority = YES; - objc_setAssociatedObject(self, ASDisplayNodeDrawingPriorityKey, [NSNumber numberWithInteger:drawingPriority], OBJC_ASSOCIATION_RETAIN); + objc_setAssociatedObject(self, ASDisplayNodeDrawingPriorityKey, @(drawingPriority), OBJC_ASSOCIATION_RETAIN); } } @@ -2522,64 +2603,31 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; _flags.isInHierarchy = inHierarchy; } -+ (dispatch_queue_t)asyncSizingQueue -{ - static dispatch_queue_t asyncSizingQueue = NULL; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - 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)); - }); - - return asyncSizingQueue; -} - -- (BOOL)_isMarkedForReplacement +- (BOOL)_hasTransitionsInProgress { ASDN::MutexLocker l(_propertyLock); - - return _replaceAsyncSentinel != nil; + return _transitionSentinel != nil; } -// FIXME: This method doesn't appear to be called, and could be removed. -// However, it may be useful for an API similar to what Paper used to create a new node hierarchy, -// trigger asynchronous measurement and display on it, and have it swap out and replace an old hierarchy. -- (ASSentinel *)_asyncReplaceSentinel +- (void)_invalidateTransitionSentinel { ASDN::MutexLocker l(_propertyLock); + _transitionSentinel = nil; +} - if (!_replaceAsyncSentinel) { - _replaceAsyncSentinel = [[ASSentinel alloc] init]; +- (BOOL)_shouldAbortTransitionWithID:(int32_t)transitionID +{ + ASDN::MutexLocker l(_propertyLock); + return _transitionSentinel == nil || transitionID != _transitionSentinel.value; +} + +- (int32_t)_newTransitionID +{ + ASDN::MutexLocker l(_propertyLock); + if (!_transitionSentinel) { + _transitionSentinel = [[ASSentinel alloc] init]; } - return _replaceAsyncSentinel; -} - -// Calls completion with nil to indicated cancellation -- (void)_enqueueAsyncSizingWithSentinel:(ASSentinel *)sentinel completion:(void(^)(ASDisplayNode *n))completion; -{ - int32_t sentinelValue = sentinel.value; - - // This is what we're going to use for sizing. Hope you like it :D - CGRect bounds = self.bounds; - - dispatch_async([[self class] asyncSizingQueue], ^{ - // Check sentinel before, bail early - if (sentinel.value != sentinelValue) - return dispatch_async(dispatch_get_main_queue(), ^{ completion(nil); }); - - [self measure:bounds.size]; - - // Check sentinel after, bail early - if (sentinel.value != sentinelValue) - return dispatch_async(dispatch_get_main_queue(), ^{ completion(nil); }); - - // Success; not cancelled - dispatch_async(dispatch_get_main_queue(), ^{ - completion(self); - }); - }); - + return [_transitionSentinel increment]; } - (id)finalLayoutable @@ -2587,6 +2635,37 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; return self; } +#pragma mark - ASEnvironment + +- (ASEnvironmentState)environmentState +{ + return _environmentState; +} + +- (void)setEnvironmentState:(ASEnvironmentState)environmentState +{ + _environmentState = environmentState; +} + +- (ASDisplayNode *)parent +{ + return self.supernode; +} + +- (NSArray *)children +{ + return self.subnodes; +} + +- (BOOL)supportsUpwardPropagation +{ + return ASEnvironmentStatePropagationEnabled(); +} + +ASEnvironmentLayoutOptionsForwarding +ASEnvironmentLayoutExtensibilityForwarding + + #if TARGET_OS_TV #pragma mark - UIFocusEnvironment Protocol (tvOS) diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.h b/AsyncDisplayKit/ASDisplayNodeExtras.h index 319b5ff62d..599ab3d750 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.h +++ b/AsyncDisplayKit/ASDisplayNodeExtras.h @@ -59,6 +59,11 @@ NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN +/** + Returns the appropriate interface state for a given ASDisplayNode and window + */ +extern ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window); + /** Given a layer, returns the associated display node, if any. */ @@ -75,13 +80,19 @@ extern ASDisplayNode * _Nullable ASViewToDisplayNode(UIView * _Nullable view); extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node); /** - This function will walk the layer heirarchy, spanning discontinuous sections of the node heirarchy (e.g. the layers + This function will walk the layer hierarchy, spanning discontinuous sections of the node hierarchy (e.g. the layers of UIKit intermediate views in UIViewControllers, UITableView, UICollectionView). In the event that a node's backing layer is not created yet, the function will only walk the direct subnodes instead - of forcing the layer heirarchy to be created. + of forcing the layer hierarchy to be created. */ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node)); +/** + This function will walk the node hierarchy in a breadth first fashion. It does run the block on the node provided + directly to the function call. + */ +extern void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^block)(ASDisplayNode *node)); + /** Identical to ASDisplayNodePerformBlockOnEveryNode, except it does not run the block on the node provided directly to the function call - only on all descendants. @@ -91,12 +102,12 @@ extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^b /** Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block. */ -extern id _Nullable ASDisplayNodeFind(ASDisplayNode * _Nullable node, BOOL (^block)(ASDisplayNode *node)); +extern ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernode(ASDisplayNode * _Nullable node, BOOL (^block)(ASDisplayNode *node)); /** Given a display node, traverses up the layer tree hierarchy, returning the first display node of kind class. */ -extern id _Nullable ASDisplayNodeFindClass(ASDisplayNode *start, Class c); +extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c); /** * Given two nodes, finds their most immediate common parent. Used for geometry conversion methods. @@ -109,7 +120,7 @@ extern id _Nullable ASDisplayNodeFindClass(ASDisplayNode *start, Class c); extern ASDisplayNode * _Nullable ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2); /** - Given a display node, collects all descendents. This is a specialization of ASCollectContainer() that walks the Core Animation layer tree as opposed to the display node tree, thus supporting non-continues display node hierarchies. + Given a display node, collects all descendants. This is a specialization of ASCollectContainer() that walks the Core Animation layer tree as opposed to the display node tree, thus supporting non-continues display node hierarchies. */ extern NSArray *ASCollectDisplayNodes(ASDisplayNode *node); @@ -121,17 +132,22 @@ extern NSArray *ASDisplayNodeFindAllSubnodes(ASDisplayNode *sta /** Given a display node, traverses down the node hierarchy, returning all the display nodes of kind class. */ -extern NSArray *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNode *start, Class c); +extern NSArray<__kindof ASDisplayNode *> *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNode *start, Class c); /** - Given a display node, traverses down the node hierarchy, returning the depth-first display node that pass the block. + Given a display node, traverses down the node hierarchy, returning the depth-first display node, including the start node that pass the block. */ -extern __kindof ASDisplayNode * ASDisplayNodeFindFirstSubnode(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node)); +extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstNode(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node)); + +/** + Given a display node, traverses down the node hierarchy, returning the depth-first display node, excluding the start node, that pass the block + */ +extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSubnode(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node)); /** Given a display node, traverses down the node hierarchy, returning the depth-first display node of kind class. */ -extern __kindof ASDisplayNode * ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c); +extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c); extern UIColor *ASDisplayNodeDefaultPlaceholderColor(); extern UIColor *ASDisplayNodeDefaultTintColor(); diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.mm b/AsyncDisplayKit/ASDisplayNodeExtras.mm index 67e9185d4e..95f7e6c5d4 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.mm +++ b/AsyncDisplayKit/ASDisplayNodeExtras.mm @@ -10,6 +10,23 @@ #import "ASDisplayNodeInternal.h" #import "ASDisplayNode+FrameworkPrivate.h" +#import + +extern ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window) +{ + ASDisplayNodeCAssert(![displayNode isLayerBacked], @"displayNode must not be layer backed as it may have a nil window"); + if (displayNode && [displayNode supportsRangeManagedInterfaceState]) { + // Directly clear the visible bit if we are not in a window. This means that the interface state is, + // if not already, about to be set to invisible as it is not possible for an element to be visible + // while outside of a window. + ASInterfaceState interfaceState = displayNode.interfaceState; + return (window == nil ? (interfaceState &= (~ASInterfaceStateVisible)) : interfaceState); + } else { + // For not range managed nodes we might be on our own to try to guess if we're visible. + return (window == nil ? ASInterfaceStateNone : (ASInterfaceStateVisible | ASInterfaceStateDisplay)); + } +} + extern ASDisplayNode *ASLayerToDisplayNode(CALayer *layer) { return layer.asyncdisplaykit_node; @@ -36,7 +53,9 @@ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode * } if (layer) { - for (CALayer *sublayer in [layer sublayers]) { + /// NOTE: The docs say `sublayers` returns a copy, but it does not. + /// See: http://stackoverflow.com/questions/14854480/collection-calayerarray-0x1ed8faa0-was-mutated-while-being-enumerated + for (CALayer *sublayer in [[layer sublayers] copy]) { ASDisplayNodePerformBlockOnEveryNode(sublayer, nil, block); } } else if (node) { @@ -46,6 +65,24 @@ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode * } } +extern void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^block)(ASDisplayNode *node)) +{ + // Queue used to keep track of subnodes while traversing this layout in a BFS fashion. + std::queue queue; + queue.push(node); + + while (!queue.empty()) { + node = queue.front(); + queue.pop(); + + block(node); + + // Add all subnodes to process in next step + for (int i = 0; i < node.subnodes.count; i++) + queue.push(node.subnodes[i]); + } +} + extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^block)(ASDisplayNode *node)) { for (ASDisplayNode *subnode in node.subnodes) { @@ -53,7 +90,7 @@ extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^b } } -id ASDisplayNodeFind(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node)) +ASDisplayNode *ASDisplayNodeFindFirstSupernode(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node)) { CALayer *layer = node.layer; @@ -68,9 +105,9 @@ id ASDisplayNodeFind(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node)) return nil; } -id ASDisplayNodeFindClass(ASDisplayNode *start, Class c) +__kindof ASDisplayNode *ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c) { - return ASDisplayNodeFind(start, ^(ASDisplayNode *n) { + return ASDisplayNodeFindFirstSupernode(start, ^(ASDisplayNode *n) { return [n isKindOfClass:c]; }); } @@ -119,7 +156,7 @@ extern NSArray *ASDisplayNodeFindAllSubnodes(ASDisplayNode *sta return list; } -extern NSArray *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNode *start, Class c) +extern NSArray<__kindof ASDisplayNode *> *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNode *start, Class c) { return ASDisplayNodeFindAllSubnodes(start, ^(ASDisplayNode *n) { return [n isKindOfClass:c]; @@ -128,10 +165,10 @@ extern NSArray *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNo #pragma mark - Find first subnode -static ASDisplayNode *_ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, BOOL includeStartNode, BOOL (^block)(ASDisplayNode *node)) +static ASDisplayNode *_ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL includeStartNode, BOOL (^block)(ASDisplayNode *node)) { for (ASDisplayNode *subnode in startNode.subnodes) { - ASDisplayNode *foundNode = _ASDisplayNodeFindFirstSubnode(subnode, YES, block); + ASDisplayNode *foundNode = _ASDisplayNodeFindFirstNode(subnode, YES, block); if (foundNode) { return foundNode; } @@ -143,21 +180,26 @@ static ASDisplayNode *_ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, B return nil; } -extern __kindof ASDisplayNode * ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node)) +extern __kindof ASDisplayNode *ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node)) { - return _ASDisplayNodeFindFirstSubnode(startNode, NO, block); + return _ASDisplayNodeFindFirstNode(startNode, YES, block); } -extern __kindof ASDisplayNode * ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c) +extern __kindof ASDisplayNode *ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node)) +{ + return _ASDisplayNodeFindFirstNode(startNode, NO, block); +} + +extern __kindof ASDisplayNode *ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c) { return ASDisplayNodeFindFirstSubnode(start, ^(ASDisplayNode *n) { return [n isKindOfClass:c]; }); } -static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possibleAncestor, ASDisplayNode *possibleDescendent) +static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possibleAncestor, ASDisplayNode *possibleDescendant) { - ASDisplayNode *supernode = possibleDescendent; + ASDisplayNode *supernode = possibleDescendant; while (supernode) { if (supernode == possibleAncestor) { return YES; diff --git a/AsyncDisplayKit/ASEditableTextNode.h b/AsyncDisplayKit/ASEditableTextNode.h index 832611c094..22ba5822a0 100644 --- a/AsyncDisplayKit/ASEditableTextNode.h +++ b/AsyncDisplayKit/ASEditableTextNode.h @@ -7,6 +7,7 @@ */ #import +#import NS_ASSUME_NONNULL_BEGIN @@ -18,7 +19,25 @@ NS_ASSUME_NONNULL_BEGIN */ @interface ASEditableTextNode : ASDisplayNode -// @abstract The text node's delegate, which must conform to the protocol. +/** + * @abstract Initializes an editable text node using default TextKit components. + * + * @returns An initialized ASEditableTextNode. + */ +- (instancetype)init; + +/** + * @abstract Initializes an editable text node using the provided TextKit components. + * + * @param textKitComponents The TextKit stack used to render text. + * @param placeholderTextKitComponents The TextKit stack used to render placeholder text. + * + * @returns An initialized ASEditableTextNode. + */ +- (instancetype)initWithTextKitComponents:(ASTextKitComponents *)textKitComponents + placeholderTextKitComponents:(ASTextKitComponents *)placeholderTextKitComponents; + +//! @abstract The text node's delegate, which must conform to the protocol. @property (nonatomic, readwrite, weak) id delegate; #pragma mark - Configuration @@ -66,12 +85,12 @@ NS_ASSUME_NONNULL_BEGIN //! @abstract The text input mode used by the receiver's keyboard, if it is visible. This value is undefined if the receiver is not the first responder. @property (nonatomic, readonly) UITextInputMode *textInputMode; -/* +/** @abstract The textContainerInset of both the placeholder and typed textView. This value defaults to UIEdgeInsetsZero. */ @property (nonatomic, readwrite) UIEdgeInsets textContainerInset; -/* +/** @abstract The returnKeyType of the keyboard. This value defaults to UIReturnKeyDefault. */ @property (nonatomic, readwrite) UIReturnKeyType returnKeyType; @@ -129,7 +148,7 @@ NS_ASSUME_NONNULL_BEGIN @abstract Indicates to the delegate that the text node's selection has changed. @param editableTextNode An editable text node. @param fromSelectedRange The previously selected range. - @param toSelectedRange The current selected range. Equvialent to the property. + @param toSelectedRange The current selected range. Equivalent to the property. @param dueToEditing YES if the selection change was due to editing; NO otherwise. @discussion You can access the selection of the receiver via . */ diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index c098e8729e..ba0b0b1f99 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -12,7 +12,6 @@ #import "ASDisplayNode+Subclasses.h" #import "ASEqualityHelpers.h" -#import "ASTextKitHelpers.h" #import "ASTextNodeWordKerner.h" #import "ASThread.h" @@ -45,6 +44,8 @@ - (void)setScrollEnabled:(BOOL)scrollEnabled { _shouldBlockPanGesture = !scrollEnabled; + self.scrollsToTop = scrollEnabled; + [super setScrollEnabled:YES]; } @@ -93,21 +94,29 @@ #pragma mark - NSObject Overrides - (instancetype)init +{ + return [self initWithTextKitComponents:[ASTextKitComponents componentsWithAttributedSeedString:nil textContainerSize:CGSizeZero] + placeholderTextKitComponents:[ASTextKitComponents componentsWithAttributedSeedString:nil textContainerSize:CGSizeZero]]; +} + +- (instancetype)initWithTextKitComponents:(ASTextKitComponents *)textKitComponents + placeholderTextKitComponents:(ASTextKitComponents *)placeholderTextKitComponents { if (!(self = [super init])) return nil; _displayingPlaceholder = YES; + _scrollEnabled = YES; // Create the scaffolding for the text view. - _textKitComponents = [ASTextKitComponents componentsWithAttributedSeedString:nil textContainerSize:CGSizeZero]; + _textKitComponents = textKitComponents; _textKitComponents.layoutManager.delegate = self; _wordKerner = [[ASTextNodeWordKerner alloc] init]; _returnKeyType = UIReturnKeyDefault; _textContainerInset = UIEdgeInsetsZero; // Create the placeholder scaffolding. - _placeholderTextKitComponents = [ASTextKitComponents componentsWithAttributedSeedString:nil textContainerSize:CGSizeZero]; + _placeholderTextKitComponents = placeholderTextKitComponents; _placeholderTextKitComponents.layoutManager.delegate = self; return self; @@ -161,7 +170,7 @@ // Create and configure our text view. _textKitComponents.textView = self.textView; - //_textKitComponents.textView = NO; // Unfortunately there's a bug here with iOS 7 DP5 that causes the text-view to only be one line high when scrollEnabled is NO. rdar://14729288 + _textKitComponents.textView.scrollEnabled = _scrollEnabled; _textKitComponents.textView.delegate = self; #if TARGET_OS_IOS _textKitComponents.textView.editable = YES; @@ -186,6 +195,7 @@ { ASDisplayNodeAssertMainThread(); + [super layout]; [self _layoutTextView]; } @@ -308,7 +318,7 @@ if (ASObjectIsEqual(_placeholderTextKitComponents.textStorage, attributedPlaceholderText)) return; - [_placeholderTextKitComponents.textStorage setAttributedString:attributedPlaceholderText ?: [[NSAttributedString alloc] initWithString:@""]]; + [_placeholderTextKitComponents.textStorage setAttributedString:attributedPlaceholderText ? : [[NSAttributedString alloc] initWithString:@""]]; _textKitComponents.textView.accessibilityHint = attributedPlaceholderText.string; } @@ -331,7 +341,7 @@ // If we (_cmd) are called while the text view itself is updating (-textViewDidUpdate:), you cannot update the text storage and expect perfect propagation to the text view. // Thus, we always update the textview directly if it's been created already. - if (ASObjectIsEqual((_textKitComponents.textView.attributedText ?: _textKitComponents.textStorage), attributedText)) + if (ASObjectIsEqual((_textKitComponents.textView.attributedText ? : _textKitComponents.textStorage), attributedText)) return; // If the cursor isn't at the end of the text, we need to preserve the selected range to avoid moving the cursor. diff --git a/AsyncDisplayKit/ASEqualityHashHelpers.mm b/AsyncDisplayKit/ASEqualityHashHelpers.mm index ca159fc980..decce0064c 100644 --- a/AsyncDisplayKit/ASEqualityHashHelpers.mm +++ b/AsyncDisplayKit/ASEqualityHashHelpers.mm @@ -10,11 +10,6 @@ #import "ASEqualityHashHelpers.h" -#import -#import -#import -#import - NSUInteger ASIntegerArrayHash(const NSUInteger *subhashes, NSUInteger count) { uint64_t result = subhashes[0]; diff --git a/AsyncDisplayKit/ASImageNode+AnimatedImage.h b/AsyncDisplayKit/ASImageNode+AnimatedImage.h new file mode 100644 index 0000000000..ae6ee4e7c0 --- /dev/null +++ b/AsyncDisplayKit/ASImageNode+AnimatedImage.h @@ -0,0 +1,15 @@ +// +// ASImageNode+AnimatedImage.h +// AsyncDisplayKit +// +// Created by Garrett Moon on 3/22/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASImageNode.h" +#import "ASImageProtocols.h" + +@interface ASImageNode (AnimatedImage) +@property (atomic, assign) BOOL animatedImagePaused; +@property (nullable, atomic, strong) id animatedImage; +@end diff --git a/AsyncDisplayKit/ASImageNode+AnimatedImage.mm b/AsyncDisplayKit/ASImageNode+AnimatedImage.mm new file mode 100644 index 0000000000..ae296ab608 --- /dev/null +++ b/AsyncDisplayKit/ASImageNode+AnimatedImage.mm @@ -0,0 +1,226 @@ +// +// ASImageNode+AnimatedImage.m +// AsyncDisplayKit +// +// Created by Garrett Moon on 3/22/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASImageNode+AnimatedImage.h" + +#import "ASAssert.h" +#import "ASImageProtocols.h" +#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNodeExtras.h" +#import "ASEqualityHelpers.h" +#import "ASDisplayNode+FrameworkPrivate.h" +#import "ASImageNode+AnimatedImagePrivate.h" +#import "ASInternalHelpers.h" +#import "ASWeakProxy.h" + +@implementation ASImageNode (AnimatedImage) + +#pragma mark - GIF support + +- (void)setAnimatedImage:(id )animatedImage +{ + ASDN::MutexLocker l(_animatedImageLock); + if (!ASObjectIsEqual(_animatedImage, animatedImage)) { + _animatedImage = animatedImage; + } + if (animatedImage != nil) { + __weak ASImageNode *weakSelf = self; + if ([animatedImage respondsToSelector:@selector(setCoverImageReadyCallback:)]) { + animatedImage.coverImageReadyCallback = ^(UIImage *coverImage) { + [weakSelf coverImageCompleted:coverImage]; + }; + } + + animatedImage.playbackReadyCallback = ^{ + [weakSelf animatedImageFileReady]; + }; + } +} + +- (id )animatedImage +{ + ASDN::MutexLocker l(_animatedImageLock); + return _animatedImage; +} + +- (void)setAnimatedImagePaused:(BOOL)animatedImagePaused +{ + ASDN::MutexLocker l(_animatedImagePausedLock); + _animatedImagePaused = animatedImagePaused; + ASPerformBlockOnMainThread(^{ + if (animatedImagePaused) { + [self stopAnimating]; + } else { + [self startAnimating]; + } + }); +} + +- (BOOL)animatedImagePaused +{ + ASDN::MutexLocker l(_animatedImagePausedLock); + return _animatedImagePaused; +} + +- (void)coverImageCompleted:(UIImage *)coverImage +{ + BOOL setCoverImage = YES; + { + ASDN::MutexLocker l(_displayLinkLock); + if (_displayLink != nil && _displayLink.paused == NO) { + setCoverImage = NO; + } + } + + if (setCoverImage) { + self.image = coverImage; + } +} + +- (void)animatedImageFileReady +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self startAnimating]; + }); +} + +- (void)startAnimating +{ + ASDisplayNodeAssertMainThread(); + if (ASInterfaceStateIncludesVisible(self.interfaceState) == NO) { + return; + } + + if (self.animatedImagePaused) { + return; + } + + if (self.animatedImage.playbackReady == NO) { + return; + } + +#if ASAnimatedImageDebug + NSLog(@"starting animation: %p", self); +#endif + ASDN::MutexLocker l(_displayLinkLock); + if (_displayLink == nil) { + _playHead = 0; + _displayLink = [CADisplayLink displayLinkWithTarget:[ASWeakProxy weakProxyWithTarget:self] selector:@selector(displayLinkFired:)]; + _displayLink.frameInterval = self.animatedImage.frameInterval; + + [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; + } else { + _displayLink.paused = NO; + } +} + +- (void)stopAnimating +{ +#if ASAnimatedImageDebug + NSLog(@"stopping animation: %p", self); +#endif + ASDisplayNodeAssertMainThread(); + ASDN::MutexLocker l(_displayLinkLock); + _displayLink.paused = YES; + self.lastDisplayLinkFire = 0; + + [self.animatedImage clearAnimatedImageCache]; +} + +- (void)visibilityDidChange:(BOOL)isVisible +{ + [super visibilityDidChange:isVisible]; + + ASDisplayNodeAssertMainThread(); + if (isVisible) { + if (self.animatedImage.coverImageReady) { + self.image = self.animatedImage.coverImage; + } + [self startAnimating]; + } else { + [self stopAnimating]; + } +} + +- (void)__enterHierarchy +{ + [super __enterHierarchy]; + [self startAnimating]; +} + +- (void)__exitHierarchy +{ + [super __exitHierarchy]; + [self stopAnimating]; +} + +- (void)displayLinkFired:(CADisplayLink *)displayLink +{ + ASDisplayNodeAssertMainThread(); + + CFTimeInterval timeBetweenLastFire; + if (self.lastDisplayLinkFire == 0) { + timeBetweenLastFire = 0; + } else { + timeBetweenLastFire = CACurrentMediaTime() - self.lastDisplayLinkFire; + } + self.lastDisplayLinkFire = CACurrentMediaTime(); + + _playHead += timeBetweenLastFire; + + while (_playHead > self.animatedImage.totalDuration) { + _playHead -= self.animatedImage.totalDuration; + _playedLoops++; + } + + if (self.animatedImage.loopCount > 0 && _playedLoops >= self.animatedImage.loopCount) { + [self stopAnimating]; + return; + } + + NSUInteger frameIndex = [self frameIndexAtPlayHeadPosition:_playHead]; + CGImageRef frameImage = [self.animatedImage imageAtIndex:frameIndex]; + + if (frameImage == nil) { + _playHead -= timeBetweenLastFire; + //Pause the display link until we get a file ready notification + displayLink.paused = YES; + self.lastDisplayLinkFire = 0; + } else { + self.contents = (__bridge id)frameImage; + } +} + +- (NSUInteger)frameIndexAtPlayHeadPosition:(CFTimeInterval)playHead +{ + ASDisplayNodeAssertMainThread(); + NSUInteger frameIndex = 0; + for (NSUInteger durationIndex = 0; durationIndex < self.animatedImage.frameCount; durationIndex++) { + playHead -= [self.animatedImage durationAtIndex:durationIndex]; + if (playHead < 0) { + return frameIndex; + } + frameIndex++; + } + + return frameIndex; +} + +- (void)dealloc +{ + ASDN::MutexLocker l(_displayLinkLock); +#if ASAnimatedImageDebug + if (_displayLink) { + NSLog(@"invalidating display link"); + } +#endif + [_displayLink invalidate]; + _displayLink = nil; +} + +@end diff --git a/AsyncDisplayKit/ASImageNode.h b/AsyncDisplayKit/ASImageNode.h index 0012350331..8ce01b6397 100644 --- a/AsyncDisplayKit/ASImageNode.h +++ b/AsyncDisplayKit/ASImageNode.h @@ -33,7 +33,7 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); * the layer's contentsCenter property. Non-stretchable images work too, of * course. */ -@property (nullable, atomic, retain) UIImage *image; +@property (nullable, atomic, strong) UIImage *image; /** @abstract The placeholder color. @@ -94,7 +94,7 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); * @discussion Can be used to add image effects (such as rounding, adding * borders, or other pattern overlays) without extraneous display calls. */ -@property (nonatomic, readwrite, copy) asimagenode_modification_block_t imageModificationBlock; +@property (nullable, nonatomic, readwrite, copy) asimagenode_modification_block_t imageModificationBlock; /** * @abstract Marks the receiver as needing display and performs a block after @@ -131,7 +131,7 @@ asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat * @abstract Image modification block that applies a tint color à la UIImage configured with * renderingMode set to UIImageRenderingModeAlwaysTemplate. * - * @param tintColor The color to tint the image. + * @param color The color to tint the image. * * @see * diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index ec18a4d49c..2c82dd9446 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -15,18 +15,23 @@ #import #import #import +#import +#import +#import #import "ASImageNode+CGExtras.h" +#import "AsyncDisplayKit+Debug.h" #import "ASInternalHelpers.h" #import "ASEqualityHelpers.h" @interface _ASImageNodeDrawParameters : NSObject +@property (nonatomic, retain) UIImage *image; @property (nonatomic, assign) BOOL opaque; @property (nonatomic, assign) CGRect bounds; @property (nonatomic, assign) CGFloat contentsScale; -@property (nonatomic, retain) UIColor *backgroundColor; +@property (nonatomic, strong) UIColor *backgroundColor; @property (nonatomic, assign) UIViewContentMode contentMode; @end @@ -34,11 +39,17 @@ // TODO: eliminate explicit parameters with a set of keys copied from the node @implementation _ASImageNodeDrawParameters -- (id)initWithBounds:(CGRect)bounds opaque:(BOOL)opaque contentsScale:(CGFloat)contentsScale backgroundColor:(UIColor *)backgroundColor contentMode:(UIViewContentMode)contentMode +- (instancetype)initWithImage:(UIImage *)image + bounds:(CGRect)bounds + opaque:(BOOL)opaque + contentsScale:(CGFloat)contentsScale + backgroundColor:(UIColor *)backgroundColor + contentMode:(UIViewContentMode)contentMode { - self = [self init]; - if (!self) return nil; + if (!(self = [self init])) + return nil; + _image = image; _opaque = opaque; _bounds = bounds; _contentsScale = contentsScale; @@ -55,7 +66,6 @@ @end - @implementation ASImageNode { @private @@ -74,12 +84,14 @@ BOOL _forceUpscaling; //Defaults to NO. CGRect _cropRect; // Defaults to CGRectMake(0.5, 0.5, 0, 0) CGRect _cropDisplayBounds; + + ASTextNode *_debugLabelNode; } @synthesize image = _image; @synthesize imageModificationBlock = _imageModificationBlock; -- (id)init +- (instancetype)init { if (!(self = [super init])) return nil; @@ -94,7 +106,7 @@ _cropRect = CGRectMake(0.5, 0.5, 0, 0); _cropDisplayBounds = CGRectNull; _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); - + return self; } @@ -124,13 +136,28 @@ - (void)setImage:(UIImage *)image { - ASDN::MutexLocker l(_imageLock); + _imageLock.lock(); if (!ASObjectIsEqual(_image, image)) { _image = image; - ASDN::MutexUnlocker u(_imageLock); + _imageLock.unlock(); + [self invalidateCalculatedLayout]; - [self setNeedsDisplay]; + if (image) { + [self setNeedsDisplay]; + + if ([ASImageNode shouldShowImageScalingOverlay]) { + ASPerformBlockOnMainThread(^{ + _debugLabelNode = [[ASTextNode alloc] init]; + _debugLabelNode.layerBacked = YES; + [self addSubnode:_debugLabelNode]; + }); + } + } else { + self.contents = nil; + } + } else { + _imageLock.unlock(); // We avoid using MutexUnlocker as it needlessly re-locks at the end of the scope. } } @@ -150,30 +177,42 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { - return [[_ASImageNodeDrawParameters alloc] initWithBounds:self.bounds - opaque:self.opaque - contentsScale:self.contentsScaleForDisplay - backgroundColor:self.backgroundColor - contentMode:self.contentMode]; + return [[_ASImageNodeDrawParameters alloc] initWithImage:self.image + bounds:self.bounds + opaque:self.opaque + contentsScale:self.contentsScaleForDisplay + backgroundColor:self.backgroundColor + contentMode:self.contentMode]; +} + +- (NSDictionary *)debugLabelAttributes +{ + return @{ NSFontAttributeName: [UIFont systemFontOfSize:15.0], + NSForegroundColorAttributeName: [UIColor redColor] }; } - (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled { - UIImage *image; - BOOL cropEnabled; - BOOL forceUpscaling; - CGFloat contentsScale; - CGRect cropDisplayBounds; - CGRect cropRect; + UIImage *image = parameters.image; + if (!image) { + return nil; + } + + BOOL forceUpscaling = NO; + BOOL cropEnabled = NO; + BOOL isOpaque = parameters.opaque; + UIColor *backgroundColor = parameters.backgroundColor; + UIViewContentMode contentMode = parameters.contentMode; + CGFloat contentsScale = 0.0; + CGRect cropDisplayBounds = CGRectZero; + CGRect cropRect = CGRectZero; asimagenode_modification_block_t imageModificationBlock; { ASDN::MutexLocker l(_imageLock); - image = _image; - if (!image) { - return nil; - } + // FIXME: There is a small risk of these values changing between the main thread creation of drawParameters, and the execution of this method. + // We should package these up into the draw parameters object. Might be easiest to create a struct for the non-objects and make it one property. cropEnabled = _cropEnabled; forceUpscaling = _forceUpscaling; contentsScale = _contentsScaleForDisplay; @@ -182,16 +221,12 @@ imageModificationBlock = _imageModificationBlock; } + BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds); + CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : parameters.bounds); + ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentWithRenderingContext; ASDisplayNodeContextModifier postContextBlock = self.didDisplayNodeContentWithRenderingContext; - BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds); - - CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : parameters.bounds); - BOOL isOpaque = parameters.opaque; - UIColor *backgroundColor = parameters.backgroundColor; - UIViewContentMode contentMode = parameters.contentMode; - ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time"); // if the image is resizable, bail early since the image has likely already been configured @@ -207,17 +242,28 @@ CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale); CGSize boundsSizeInPixels = CGSizeMake(floorf(bounds.size.width * contentsScale), floorf(bounds.size.height * contentsScale)); - BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill - || contentMode == UIViewContentModeScaleAspectFit - || contentMode == UIViewContentModeCenter; + if (_debugLabelNode) { + CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height); + if (pixelCountRatio != 1.0) { + NSString *scaleString = [NSString stringWithFormat:@"%.2fx", pixelCountRatio]; + _debugLabelNode.attributedString = [[NSAttributedString alloc] initWithString:scaleString attributes:[self debugLabelAttributes]]; + _debugLabelNode.hidden = NO; + [self setNeedsLayout]; + } else { + _debugLabelNode.hidden = YES; + _debugLabelNode.attributedString = nil; + } + } - CGSize backingSize; - CGRect imageDrawRect; + BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill || + contentMode == UIViewContentModeScaleAspectFit || + contentMode == UIViewContentModeCenter; - if (boundsSizeInPixels.width * contentsScale < 1.0f || - boundsSizeInPixels.height * contentsScale < 1.0f || - imageSizeInPixels.width < 1.0f || - imageSizeInPixels.height < 1.0f) { + CGSize backingSize = CGSizeZero; + CGRect imageDrawRect = CGRectZero; + + if (boundsSizeInPixels.width * contentsScale < 1.0f || boundsSizeInPixels.height * contentsScale < 1.0f || + imageSizeInPixels.width < 1.0f || imageSizeInPixels.height < 1.0f) { return nil; } @@ -235,10 +281,8 @@ &imageDrawRect); } - if (backingSize.width <= 0.0f || - backingSize.height <= 0.0f || - imageDrawRect.size.width <= 0.0f || - imageDrawRect.size.height <= 0.0f) { + if (backingSize.width <= 0.0f || backingSize.height <= 0.0f || + imageDrawRect.size.width <= 0.0f || imageDrawRect.size.height <= 0.0f) { return nil; } @@ -583,9 +627,21 @@ #endif +#pragma mark - Debug +- (void)layout +{ + [super layout]; + + if (_debugLabelNode) { + CGSize boundsSize = self.bounds.size; + CGSize debugLabelSize = [_debugLabelNode measure:boundsSize]; + CGPoint debugLabelOrigin = CGPointMake(boundsSize.width - debugLabelSize.width, + boundsSize.height - debugLabelSize.height); + _debugLabelNode.frame = (CGRect) {debugLabelOrigin, debugLabelSize}; + } +} @end - #pragma mark - Extras extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor) { @@ -633,4 +689,3 @@ extern asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UI return modifiedImage; }; } - diff --git a/AsyncDisplayKit/ASImageNode.mm.orig b/AsyncDisplayKit/ASImageNode.mm.orig new file mode 100644 index 0000000000..a5747149a9 --- /dev/null +++ b/AsyncDisplayKit/ASImageNode.mm.orig @@ -0,0 +1,694 @@ +/* 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 "ASImageNode.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import "ASImageNode+CGExtras.h" +#import "AsyncDisplayKit+Debug.h" + +#import "ASInternalHelpers.h" +#import "ASEqualityHelpers.h" + +@interface _ASImageNodeDrawParameters : NSObject + +@property (nonatomic, retain) UIImage *image; +@property (nonatomic, assign) BOOL opaque; +@property (nonatomic, assign) CGRect bounds; +@property (nonatomic, assign) CGFloat contentsScale; +@property (nonatomic, strong) UIColor *backgroundColor; +@property (nonatomic, assign) UIViewContentMode contentMode; + +@end + +// TODO: eliminate explicit parameters with a set of keys copied from the node +@implementation _ASImageNodeDrawParameters + +- (instancetype)initWithImage:(UIImage *)image + bounds:(CGRect)bounds + opaque:(BOOL)opaque + contentsScale:(CGFloat)contentsScale + backgroundColor:(UIColor *)backgroundColor + contentMode:(UIViewContentMode)contentMode +{ + if (!(self = [self init])) + return nil; + + _image = image; + _opaque = opaque; + _bounds = bounds; + _contentsScale = contentsScale; + _backgroundColor = backgroundColor; + _contentMode = contentMode; + + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@ : %p opaque:%@ bounds:%@ contentsScale:%.2f backgroundColor:%@ contentMode:%@>", [self class], self, @(self.opaque), NSStringFromCGRect(self.bounds), self.contentsScale, self.backgroundColor, ASDisplayNodeNSStringFromUIContentMode(self.contentMode)]; +} + +@end + +@implementation ASImageNode +{ +@private + UIImage *_image; + + void (^_displayCompletionBlock)(BOOL canceled); + ASDN::RecursiveMutex _imageLock; + +#if TARGET_OS_TV + //tvOS + BOOL isDefaultState; +#endif + + // Cropping. + BOOL _cropEnabled; // Defaults to YES. + BOOL _forceUpscaling; //Defaults to NO. + CGRect _cropRect; // Defaults to CGRectMake(0.5, 0.5, 0, 0) + CGRect _cropDisplayBounds; + + ASTextNode *_debugLabelNode; +} + +@synthesize image = _image; +@synthesize imageModificationBlock = _imageModificationBlock; + +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + // TODO can this be removed? + self.contentsScale = ASScreenScale(); + self.contentMode = UIViewContentModeScaleAspectFill; + self.opaque = NO; + + _cropEnabled = YES; + _forceUpscaling = NO; + _cropRect = CGRectMake(0.5, 0.5, 0, 0); + _cropDisplayBounds = CGRectNull; + _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); + + return self; +} + +- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock +{ + ASDisplayNodeAssertNotSupported(); + return nil; +} + +- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock +{ + ASDisplayNodeAssertNotSupported(); + return nil; +} + +- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize +{ + ASDN::MutexLocker l(_imageLock); + // if a preferredFrameSize is set, call the superclass to return that instead of using the image size. + if (CGSizeEqualToSize(self.preferredFrameSize, CGSizeZero) == NO) + return [super calculateSizeThatFits:constrainedSize]; + else if (_image) + return _image.size; + else + return CGSizeZero; +} + +- (void)setImage:(UIImage *)image +{ + _imageLock.lock(); + if (!ASObjectIsEqual(_image, image)) { + _image = image; + + _imageLock.unlock(); + + [self invalidateCalculatedLayout]; + if (image) { + [self setNeedsDisplay]; + + if ([ASImageNode shouldShowImageScalingOverlay]) { + ASPerformBlockOnMainThread(^{ + _debugLabelNode = [[ASTextNode alloc] init]; + _debugLabelNode.layerBacked = YES; + [self addSubnode:_debugLabelNode]; + }); + } + } else { + self.contents = nil; + } + } else { + _imageLock.unlock(); // We avoid using MutexUnlocker as it needlessly re-locks at the end of the scope. + } +} + +- (UIImage *)image +{ + ASDN::MutexLocker l(_imageLock); + return _image; +} + +- (void)setPlaceholderColor:(UIColor *)placeholderColor +{ + _placeholderColor = placeholderColor; + + // prevent placeholders if we don't have a color + self.placeholderEnabled = placeholderColor != nil; +} + +- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer +{ + return [[_ASImageNodeDrawParameters alloc] initWithImage:self.image + bounds:self.bounds + opaque:self.opaque + contentsScale:self.contentsScaleForDisplay + backgroundColor:self.backgroundColor + contentMode:self.contentMode]; +} + +- (NSDictionary *)debugLabelAttributes +{ + return @{ NSFontAttributeName: [UIFont systemFontOfSize:15.0], + NSForegroundColorAttributeName: [UIColor redColor] }; +} + +- (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled +{ + UIImage *image = parameters.image; + if (!image) { + return nil; + } + + BOOL forceUpscaling = NO; + BOOL cropEnabled = NO; + BOOL isOpaque = parameters.opaque; + UIColor *backgroundColor = parameters.backgroundColor; + UIViewContentMode contentMode = parameters.contentMode; + CGFloat contentsScale = 0.0; + CGRect cropDisplayBounds = CGRectZero; + CGRect cropRect = CGRectZero; + asimagenode_modification_block_t imageModificationBlock; + + { + ASDN::MutexLocker l(_imageLock); + + // FIXME: There is a small risk of these values changing between the main thread creation of drawParameters, and the execution of this method. + // We should package these up into the draw parameters object. Might be easiest to create a struct for the non-objects and make it one property. + cropEnabled = _cropEnabled; + forceUpscaling = _forceUpscaling; + contentsScale = _contentsScaleForDisplay; + cropDisplayBounds = _cropDisplayBounds; + cropRect = _cropRect; + imageModificationBlock = _imageModificationBlock; + } + + BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds); + CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : parameters.bounds); + + ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentWithRenderingContext; + ASDisplayNodeContextModifier postContextBlock = self.didDisplayNodeContentWithRenderingContext; + + ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time"); + + // if the image is resizable, bail early since the image has likely already been configured + BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero); + if (stretchable) { + if (imageModificationBlock != NULL) { + image = imageModificationBlock(image); + } + return image; + } + + CGSize imageSize = image.size; + CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale); + CGSize boundsSizeInPixels = CGSizeMake(floorf(bounds.size.width * contentsScale), floorf(bounds.size.height * contentsScale)); + + if (_debugLabelNode) { + CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height); + if (pixelCountRatio != 1.0) { + NSString *scaleString = [NSString stringWithFormat:@"%.2fx", pixelCountRatio]; + _debugLabelNode.attributedString = [[NSAttributedString alloc] initWithString:scaleString attributes:[self debugLabelAttributes]]; + _debugLabelNode.hidden = NO; + [self setNeedsLayout]; + } else { + _debugLabelNode.hidden = YES; + _debugLabelNode.attributedString = nil; + } + } + + BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill || + contentMode == UIViewContentModeScaleAspectFit || + contentMode == UIViewContentModeCenter; + + CGSize backingSize = CGSizeZero; + CGRect imageDrawRect = CGRectZero; + + if (boundsSizeInPixels.width * contentsScale < 1.0f || boundsSizeInPixels.height * contentsScale < 1.0f || + imageSizeInPixels.width < 1.0f || imageSizeInPixels.height < 1.0f) { + return nil; + } + + // If we're not supposed to do any cropping, just decode image at original size + if (!cropEnabled || !contentModeSupported || stretchable) { + backingSize = imageSizeInPixels; + imageDrawRect = (CGRect){.size = backingSize}; + } else { + ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels, + boundsSizeInPixels, + contentMode, + cropRect, + forceUpscaling, + &backingSize, + &imageDrawRect); + } + + if (backingSize.width <= 0.0f || backingSize.height <= 0.0f || + imageDrawRect.size.width <= 0.0f || imageDrawRect.size.height <= 0.0f) { + return nil; + } + + // Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds + // will do its rounding on pixel instead of point boundaries + UIGraphicsBeginImageContextWithOptions(backingSize, isOpaque, 1.0); + + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context && preContextBlock) { + preContextBlock(context); + } + + // if view is opaque, fill the context with background color + if (isOpaque && backgroundColor) { + [backgroundColor setFill]; + UIRectFill({ .size = backingSize }); + } + + // iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on + // multiple threads concurrently. In fact, instead of crashing, it appears to deadlock. + // The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premier, + // as well as iOS games, and a small number of ASDK apps that provide the same image reference + // to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes + // that may get the same pointer for a given UI asset image, etc. + // FIXME: We should replace @synchronized here, probably using a global, locked NSMutableSet, and + // only if the object already exists in the set we should create a semaphore to signal waiting threads + // upon removal of the object from the set when the operation completes. + // Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer. + // Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068 + + @synchronized(image) { + [image drawInRect:imageDrawRect]; + } + + if (context && postContextBlock) { + postContextBlock(context); + } + + if (isCancelled()) { + UIGraphicsEndImageContext(); + return nil; + } + + UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); + + UIGraphicsEndImageContext(); + + if (imageModificationBlock != NULL) { + result = imageModificationBlock(result); + } + + return result; +} + +- (void)displayDidFinish +{ + [super displayDidFinish]; + + _imageLock.lock(); + void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock; + UIImage *image = _image; + _imageLock.unlock(); + + // If we've got a block to perform after displaying, do it. + if (image && displayCompletionBlock) { + + displayCompletionBlock(NO); + + _imageLock.lock(); + _displayCompletionBlock = nil; + _imageLock.unlock(); + } +} + +#pragma mark - +- (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))displayCompletionBlock +{ + if (self.displaySuspended) { + if (displayCompletionBlock) + displayCompletionBlock(YES); + return; + } + + // Stash the block and call-site queue. We'll invoke it in -displayDidFinish. + ASDN::MutexLocker l(_imageLock); + if (_displayCompletionBlock != displayCompletionBlock) { + _displayCompletionBlock = [displayCompletionBlock copy]; + } + + [self setNeedsDisplay]; +} + +#pragma mark - Cropping +- (BOOL)isCropEnabled +{ + ASDN::MutexLocker l(_imageLock); + return _cropEnabled; +} + +- (void)setCropEnabled:(BOOL)cropEnabled +{ + [self setCropEnabled:cropEnabled recropImmediately:NO inBounds:self.bounds]; +} + +- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds +{ + ASDN::MutexLocker l(_imageLock); + if (_cropEnabled == cropEnabled) + return; + + _cropEnabled = cropEnabled; + _cropDisplayBounds = cropBounds; + + // If we have an image to display, display it, respecting our recrop flag. + if (self.image) + { + ASPerformBlockOnMainThread(^{ + if (recropImmediately) + [self displayImmediately]; + else + [self setNeedsDisplay]; + }); + } +} + +- (CGRect)cropRect +{ + ASDN::MutexLocker l(_imageLock); + return _cropRect; +} + +- (void)setCropRect:(CGRect)cropRect +{ + ASDN::MutexLocker l(_imageLock); + if (CGRectEqualToRect(_cropRect, cropRect)) + return; + + _cropRect = cropRect; + + // TODO: this logic needs to be updated to respect cropRect. + CGSize boundsSize = self.bounds.size; + CGSize imageSize = self.image.size; + + BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height)); + + // Re-display if we need to. + ASPerformBlockOnMainThread(^{ + if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage) + [self setNeedsDisplay]; + }); +} + +- (BOOL)forceUpscaling +{ + ASDN::MutexLocker l(_imageLock); + return _forceUpscaling; +} + +- (void)setForceUpscaling:(BOOL)forceUpscaling +{ + ASDN::MutexLocker l(_imageLock); + _forceUpscaling = forceUpscaling; +} + +- (asimagenode_modification_block_t)imageModificationBlock +{ + ASDN::MutexLocker l(_imageLock); + return _imageModificationBlock; +} + +- (void)setImageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock +{ + ASDN::MutexLocker l(_imageLock); + _imageModificationBlock = imageModificationBlock; +} + +<<<<<<< HEAD + +#if TARGET_OS_TV +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesBegan:touches withEvent:event]; + isDefaultState = NO; + UIView *view = [self getView]; + CALayer *layer = view.layer; + + CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); + [layer removeAllAnimations]; + [CATransaction begin]; + [CATransaction setCompletionBlock:^{ + layer.shadowOffset = targetShadowOffset; + }]; + + CABasicAnimation *shadowOffsetAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOffset"]; + shadowOffsetAnimation.toValue = [NSValue valueWithCGSize:targetShadowOffset]; + shadowOffsetAnimation.duration = 0.4; + shadowOffsetAnimation.removedOnCompletion = NO; + shadowOffsetAnimation.fillMode = kCAFillModeForwards; + shadowOffsetAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; + [layer addAnimation:shadowOffsetAnimation forKey:@"shadowOffset"]; + [CATransaction commit]; + + CABasicAnimation *shadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; + shadowOpacityAnimation.toValue = [NSNumber numberWithFloat:0.45]; + shadowOpacityAnimation.duration = 0.4; + shadowOpacityAnimation.removedOnCompletion = false; + shadowOpacityAnimation.fillMode = kCAFillModeForwards; + shadowOpacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; + [layer addAnimation:shadowOpacityAnimation forKey:@"shadowOpacityAnimation"]; + + view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); + + [CATransaction commit]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesMoved:touches withEvent:event]; + + if (!isDefaultState) { + UIView *view = [self getView]; + + UITouch *touch = [touches anyObject]; + // Get the specific point that was touched + // This is quite messy in it's current state so is not ready for production. The reason it is here is for others to contribute and to make it clear what is occuring. + // TODO: Clean up, and improve visuals. + CGPoint point = [touch locationInView:self.view]; + float pitch = 0; + float yaw = 0; + BOOL topHalf = NO; + if (point.y > CGRectGetHeight(self.view.frame)) { + pitch = 15; + } else if (point.y < -CGRectGetHeight(self.view.frame)) { + pitch = -15; + } else { + pitch = (point.y/CGRectGetHeight(self.view.frame))*15; + } + if (pitch < 0) { + topHalf = YES; + } + + if (point.x > CGRectGetWidth(self.view.frame)) { + yaw = 10; + } else if (point.x < -CGRectGetWidth(self.view.frame)) { + yaw = -10; + } else { + yaw = (point.x/CGRectGetWidth(self.view.frame))*10; + } + if (!topHalf) { + if (yaw > 0) { + yaw = -yaw; + } else { + yaw = fabsf(yaw); + } + } + + CATransform3D pitchTransform = CATransform3DMakeRotation([self degressToRadians:pitch],1.0,0.0,0.0); + CATransform3D yawTransform = CATransform3DMakeRotation([self degressToRadians:yaw],0.0,1.0,0.0); + CATransform3D transform = CATransform3DConcat(pitchTransform, yawTransform); + CATransform3D scaleAndTransform = CATransform3DConcat(transform, CATransform3DMakeAffineTransform(CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25))); + + [UIView animateWithDuration:0.5 animations:^{ + view.layer.transform = scaleAndTransform; + }]; + } else { + [self setDefaultState]; + } +} + + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesEnded:touches withEvent:event]; + [self finishTouches]; +} + +- (void)finishTouches +{ + if (!isDefaultState) { + UIView *view = [self getView]; + CALayer *layer = view.layer; + + CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); + CATransform3D targetScaleTransform = CATransform3DMakeScale(1.2, 1.2, 1.2); + [CATransaction begin]; + [CATransaction setCompletionBlock:^{ + layer.shadowOffset = targetShadowOffset; + }]; + [CATransaction commit]; + + [UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ + view.layer.transform = targetScaleTransform; + } completion:^(BOOL finished) { + if (finished) { + [layer removeAnimationForKey:@"shadowOffset"]; + [layer removeAnimationForKey:@"shadowOpacity"]; + } + }]; + } else { + [self setDefaultState]; + } +} + +- (void)setFocusedState +{ + UIView *view = [self getView]; + CALayer *layer = view.layer; + layer.shadowOffset = CGSizeMake(2, 10); + layer.shadowColor = [UIColor blackColor].CGColor; + layer.shadowRadius = 12.0; + layer.shadowOpacity = 0.45; + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); +} + +- (void)setDefaultState +{ + UIView *view = [self getView]; + CALayer *layer = view.layer; + view.transform = CGAffineTransformIdentity; + layer.shadowOpacity = 0; + layer.shadowOffset = CGSizeZero; + layer.shadowRadius = 0; + layer.shadowPath = nil; + [layer removeAnimationForKey:@"shadowOffset"]; + [layer removeAnimationForKey:@"shadowOpacity"]; + isDefaultState = YES; +} + +- (UIView *)getView +{ + UIView *view = self.view; + //If we are inside a ASCellNode, then we need to apply our focus effects to the ASCellNode view/layer rather than the ASImageNode view/layer. + if (CGSizeEqualToSize(self.view.superview.frame.size, self.view.frame.size) && self.view.superview.superview) { + view = self.view.superview.superview; + } + return view; +} + +- (float)degressToRadians:(float)value +{ + return value * M_PI / 180; +} + +#endif + +======= +#pragma mark - Debug +- (void)layout +{ + [super layout]; + + if (_debugLabelNode) { + CGSize boundsSize = self.bounds.size; + CGSize debugLabelSize = [_debugLabelNode measure:boundsSize]; + CGPoint debugLabelOrigin = CGPointMake(boundsSize.width - debugLabelSize.width, + boundsSize.height - debugLabelSize.height); + _debugLabelNode.frame = (CGRect) {debugLabelOrigin, debugLabelSize}; + } +} +>>>>>>> master +@end + +#pragma mark - Extras +extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor) +{ + return ^(UIImage *originalImage) { + UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); + UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}]; + + // Make the image round + [roundOutline addClip]; + + // Draw the original image + [originalImage drawAtPoint:CGPointZero]; + + // Draw a border on top. + if (borderWidth > 0.0) { + [borderColor setStroke]; + [roundOutline setLineWidth:borderWidth]; + [roundOutline stroke]; + } + + UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return modifiedImage; + }; +} + +extern asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color) +{ + return ^(UIImage *originalImage) { + UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); + + // Set color and render template + [color setFill]; + UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [templateImage drawAtPoint:CGPointZero]; + + UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + // if the original image was stretchy, keep it stretchy + if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) { + modifiedImage = [modifiedImage resizableImageWithCapInsets:originalImage.capInsets resizingMode:originalImage.resizingMode]; + } + + return modifiedImage; + }; +} diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h index 8703ac2c1a..201ef2170f 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/AsyncDisplayKit/ASMapNode.h @@ -48,10 +48,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) id mapDelegate; /** - * @discussion This method sets the annotations of the static map view and also to the live map view. Passing an empty array clears the map of any annotations. - * @param annotations An array of objects that conform to the MKAnnotation protocol + * @abstract The annotations to display on the map. */ -- (void)setAnnotations:(NSArray> *)annotations; +@property (nonatomic, copy) NSArray> *annotations; @end diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index fcfd821487..55dbf00898 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -13,11 +13,14 @@ #import #import #import +#import +#import @interface ASMapNode() { ASDN::RecursiveMutex _propertyLock; MKMapSnapshotter *_snapshotter; + BOOL _snapshotAfterLayout; NSArray *_annotations; CLLocationCoordinate2D _centerCoordinateOfMap; } @@ -42,6 +45,7 @@ _needsMapReloadOnBoundsChange = YES; _liveMap = NO; _centerCoordinateOfMap = kCLLocationCoordinate2DInvalid; + _annotations = @[]; return self; } @@ -63,19 +67,23 @@ - (void)fetchData { [super fetchData]; - if (self.isLiveMap) { - [self addLiveMap]; - } else { - [self takeSnapshot]; - } + ASPerformBlockOnMainThread(^{ + if (self.isLiveMap) { + [self addLiveMap]; + } else { + [self takeSnapshot]; + } + }); } -- (void)clearContents +- (void)clearFetchedData { - [super clearContents]; - if (self.isLiveMap) { - [self removeLiveMap]; - } + [super clearFetchedData]; + ASPerformBlockOnMainThread(^{ + if (self.isLiveMap) { + [self removeLiveMap]; + } + }); } #pragma mark - Settings @@ -128,12 +136,14 @@ - (void)setOptions:(MKMapSnapshotOptions *)options { ASDN::MutexLocker l(_propertyLock); - _options = options; - if (self.isLiveMap) { - [self applySnapshotOptions]; - } else { - [self resetSnapshotter]; - [self takeSnapshot]; + if (!_options || ![options isEqual:_options]) { + _options = options; + if (self.isLiveMap) { + [self applySnapshotOptions]; + } else if (_snapshotter) { + [self destroySnapshotter]; + [self takeSnapshot]; + } } } @@ -151,41 +161,61 @@ - (void)takeSnapshot { + // If our size is zero, we want to avoid calling a default sized snapshot. Set _snapshotAfterLayout to YES + // so if layout changes in the future, we'll try snapshotting again. + ASLayout *layout = self.calculatedLayout; + if (layout == nil || CGSizeEqualToSize(CGSizeZero, layout.size)) { + _snapshotAfterLayout = YES; + return; + } + + _snapshotAfterLayout = NO; + if (!_snapshotter) { [self setUpSnapshotter]; } - [_snapshotter cancel]; - [_snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) { - if (!error) { - UIImage *image = snapshot.image; - CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); - - UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); - [image drawAtPoint:CGPointMake(0, 0)]; - - if (_annotations.count > 0 ) { - // Get a standard annotation view pin. Future implementations should use a custom annotation image property. - MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; - UIImage *pinImage = pin.image; - for (idannotation in _annotations) - { - CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; - if (CGRectContainsPoint(finalImageRect, point)) - { - CGPoint pinCenterOffset = pin.centerOffset; - point.x -= pin.bounds.size.width / 2.0; - point.y -= pin.bounds.size.height / 2.0; - point.x += pinCenterOffset.x; - point.y += pinCenterOffset.y; - [pinImage drawAtPoint:point]; - } - } - } - - UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - self.image = finalImage; - } + + if (_snapshotter.isLoading) { + return; + } + + [_snapshotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) + completionHandler:^(MKMapSnapshot *snapshot, NSError *error) { + if (!error) { + UIImage *image = snapshot.image; + + if (_annotations.count > 0) { + // Only create a graphics context if we have annotations to draw. + // The MKMapSnapshotter is currently not capable of rendering annotations automatically. + + CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); + + UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); + [image drawAtPoint:CGPointZero]; + + // Get a standard annotation view pin. Future implementations should use a custom annotation image property. + MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; + UIImage *pinImage = pin.image; + CGSize pinSize = pin.bounds.size; + + for (id annotation in _annotations) { + CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; + if (CGRectContainsPoint(finalImageRect, point)) { + CGPoint pinCenterOffset = pin.centerOffset; + point.x -= pinSize.width / 2.0; + point.y -= pinSize.height / 2.0; + point.x += pinCenterOffset.x; + point.y += pinCenterOffset.y; + [pinImage drawAtPoint:point]; + } + } + + image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + + self.image = image; + } }]; } @@ -195,12 +225,10 @@ _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; } -- (void)resetSnapshotter +- (void)destroySnapshotter { - // FIXME: The semantics of this method / name would suggest that we cancel + destroy the snapshotter, - // but not that we create a new one. We should probably only create the new one in -takeSnapshot or something. [_snapshotter cancel]; - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; + _snapshotter = nil; } - (void)applySnapshotOptions @@ -240,10 +268,18 @@ _mapView = nil; } -- (void)setAnnotations:(NSArray *)annotations +- (NSArray *)annotations { ASDN::MutexLocker l(_propertyLock); - _annotations = [annotations copy]; + return _annotations; +} + +- (void)setAnnotations:(NSArray *)annotations +{ + annotations = [annotations copy] ? : @[]; + + ASDN::MutexLocker l(_propertyLock); + _annotations = annotations; if (self.isLiveMap) { [_mapView removeAnnotations:_mapView.annotations]; [_mapView addAnnotations:annotations]; @@ -253,11 +289,14 @@ } #pragma mark - Layout -- (void)setSnapshotSizeIfNeeded:(CGSize)snapshotSize +- (void)setSnapshotSizeWithReloadIfNeeded:(CGSize)snapshotSize { - if (!CGSizeEqualToSize(self.options.size, snapshotSize)) { + if (snapshotSize.height > 0 && snapshotSize.width > 0 && !CGSizeEqualToSize(self.options.size, snapshotSize)) { _options.size = snapshotSize; - [self resetSnapshotter]; + if (_snapshotter) { + [self destroySnapshotter]; + [self takeSnapshot]; + } } } @@ -266,12 +305,30 @@ CGSize size = self.preferredFrameSize; if (CGSizeEqualToSize(size, CGSizeZero)) { size = constrainedSize; + + // FIXME: Need a better way to allow maps to take up the right amount of space in a layout (sizeRange, etc) + // These fallbacks protect against inheriting a constrainedSize that contains a CGFLOAT_MAX value. + if (!isValidForLayout(size.width)) { + size.width = 100.0; + } + if (!isValidForLayout(size.height)) { + size.height = 100.0; + } } - [self setSnapshotSizeIfNeeded:size]; - return constrainedSize; + [self setSnapshotSizeWithReloadIfNeeded:size]; + return size; } -// Layout isn't usually needed in the box model, but since we are making use of MKMapView this is preferred. +- (void)calculatedLayoutDidChange +{ + [super calculatedLayoutDidChange]; + + if (_snapshotAfterLayout) { + [self takeSnapshot]; + } +} + +// -layout isn't usually needed over -layoutSpecThatFits, but this way we can avoid a needless node wrapper for MKMapView. - (void)layout { [super layout]; @@ -280,10 +337,9 @@ } else { // If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter. if (_needsMapReloadOnBoundsChange) { - [self setSnapshotSizeIfNeeded:self.bounds.size]; + [self setSnapshotSizeWithReloadIfNeeded:self.bounds.size]; // FIXME: Adding a check for FetchData here seems to cause intermittent map load failures, but shouldn't. // if (ASInterfaceStateIncludesFetchData(self.interfaceState)) { - [self takeSnapshot]; } } } diff --git a/AsyncDisplayKit/ASMultiplexImageNode.h b/AsyncDisplayKit/ASMultiplexImageNode.h index 5c02ed6048..73a74cbdeb 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.h +++ b/AsyncDisplayKit/ASMultiplexImageNode.h @@ -55,7 +55,7 @@ typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) { * @abstract ASMultiplexImageNode is an image node that can load and display multiple versions of an image. For * example, it can display a low-resolution version of an image while the high-resolution version is loading. * - * @discussion ASMultiplexImageNode begins loading images when its *imageIdentifiers; /** - * @abstract Notify the receiver SSAAthat its data source has new UIImages or NSURLs available for . + * @abstract Notify the receiver SSAA that its data source has new UIImages or NSURLs available for . * * @discussion If a higher-quality image than is currently displayed is now available, it will be loaded. */ diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index 483dfb9973..b7d0c915b0 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -37,6 +37,8 @@ NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDom static NSString *const kAssetsLibraryURLScheme = @"assets-library"; +static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; + /** @abstract Signature for the block to be performed after an image has loaded. @param image The image that was loaded, or nil if no image was loaded. @@ -122,7 +124,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent @param imageIdentifier The identifier for the image to be fetched. May not be nil. @param imageURL The URL of the image to fetch. May not be nil. @param completionBlock The block to be performed when the image has been fetched from the cache, if possible. May not be nil. - @param image The image fetched from the cache, if any. + @param image The image fetched from the cache, if any. @discussion This method queries both the session's in-memory and on-disk caches (with preference for the in-memory cache). */ - (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock; @@ -133,8 +135,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent @param imageIdentifier The identifier for the image to be loaded. May not be nil. @param assetURL The assets-library URL (e.g., "assets-library://identifier") of the image to load, from ALAsset. May not be nil. @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. - @param image The image that was loaded. May be nil if no image could be downloaded. - @param error An error describing why the load failed, if it failed; nil otherwise. + @param image The image that was loaded. May be nil if no image could be downloaded. + @param error An error describing why the load failed, if it failed; nil otherwise. */ - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; @@ -143,8 +145,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent @param imageIdentifier The identifier for the image to be loaded. May not be nil. @param request The photos image request to load. May not be nil. @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. - @param image The image that was loaded. May be nil if no image could be downloaded. - @param error An error describing why the load failed, if it failed; nil otherwise. + @param image The image that was loaded. May be nil if no image could be downloaded. + @param error An error describing why the load failed, if it failed; nil otherwise. */ - (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock; #endif @@ -153,8 +155,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent @param imageIdentifier The identifier for the image to be downloaded. May not be nil. @param imageURL The URL of the image to downloaded. May not be nil. @param completionBlock The block to be performed when the image has been downloaded, if possible. May not be nil. - @param image The image that was downloaded. May be nil if no image could be downloaded. - @param error An error describing why the download failed, if it failed; nil otherwise. + @param image The image that was downloaded. May be nil if no image could be downloaded. + @param error An error describing why the download failed, if it failed; nil otherwise. */ - (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; @@ -248,7 +250,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent // Delegateify if (_delegateFlags.displayFinish) { - if ([NSThread isMainThread]) + if (ASDisplayNodeThreadIsMain()) [_delegate multiplexImageNodeDidFinishDisplay:self]; else { __weak __typeof__(self) weakSelf = self; @@ -263,6 +265,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent } } +- (BOOL)placeholderShouldPersist +{ + return (self.image == nil && self.imageIdentifiers.count > 0); +} + /* displayWillStart in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary in ASNetworkImageNode as well. */ - (void)displayWillStart @@ -403,7 +410,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent // Delegateify. // Note that we're using the params here instead of self.image and _displayedImageIdentifier because those can change before the async block below executes. if (_delegateFlags.updatedImageDisplayFinish) { - if ([NSThread isMainThread]) + if (ASDisplayNodeThreadIsMain()) [_delegate multiplexImageNode:self didDisplayUpdatedImage:image withIdentifier:displayedImageIdentifier]; else { __weak __typeof__(self) weakSelf = self; @@ -470,6 +477,23 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent return nil; } +#pragma mark - +- (void)_clearImage +{ + // Destruction of bigger images on the main thread can be expensive + // and can take some time, so we dispatch onto a bg queue to + // actually dealloc. + __block UIImage *image = self.image; + CGSize imageSize = image.size; + BOOL shouldReleaseImageOnBackgroundThread = imageSize.width > kMinReleaseImageOnBackgroundSize.width || + imageSize.height > kMinReleaseImageOnBackgroundSize.height; + if (shouldReleaseImageOnBackgroundThread) { + ASPerformBlockOnBackgroundThread(^{ + image = nil; + }); + } + self.image = nil; +} #pragma mark - - (id)_nextImageIdentifierToDownload @@ -681,7 +705,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent options.synchronous = YES; } - PHImageManager *imageManager = strongSelf.imageManager ?: PHImageManager.defaultManager; + PHImageManager *imageManager = strongSelf.imageManager ? : PHImageManager.defaultManager; [imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) { NSError *error = info[PHImageErrorKey]; @@ -714,14 +738,17 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent if (_cache) { if (_cacheSupportsNewProtocol) { - [_cache cachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(UIImage *imageFromCache) { - completionBlock(imageFromCache); + [_cache cachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(id imageContainer) { + completionBlock([imageContainer asdk_image]); }]; } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [_cache fetchCachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(CGImageRef coreGraphicsImageFromCache) { UIImage *imageFromCache = (coreGraphicsImageFromCache ? [UIImage imageWithCGImage:coreGraphicsImageFromCache] : nil); completionBlock(imageFromCache); }]; +#pragma clang diagnostic pop } } // If we don't have a cache, just fail immediately. @@ -757,7 +784,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() downloadProgress:downloadProgressBlock - completion:^(UIImage *downloadedImage, NSError *error, id downloadIdentifier) { + completion:^(id imageContainer, NSError *error, id downloadIdentifier) { // We dereference iVars directly, so we can't have weakSelf going nil on us. __typeof__(self) strongSelf = weakSelf; if (!strongSelf) @@ -769,13 +796,15 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent return; } - completionBlock(downloadedImage, error); + completionBlock([imageContainer asdk_image], error); // Delegateify. if (strongSelf->_delegateFlags.downloadFinish) [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; }]]; } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() downloadProgressBlock:downloadProgressBlock @@ -792,6 +821,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent if (strongSelf->_delegateFlags.downloadFinish) [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; }]]; +#pragma clang diagnostic pop } }); } diff --git a/AsyncDisplayKit/ASNetworkImageNode.h b/AsyncDisplayKit/ASNetworkImageNode.h index 79070aaccc..bb867f576c 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.h +++ b/AsyncDisplayKit/ASNetworkImageNode.h @@ -69,7 +69,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)setURL:(nullable NSURL *)URL resetToDefault:(BOOL)reset; /** - * If is a local file, set this property to YES to take advantage of UIKit's image cacheing. Defaults to YES. + * If is a local file, set this property to YES to take advantage of UIKit's image caching. Defaults to YES. */ @property (nonatomic, assign, readwrite) BOOL shouldCacheImage; @@ -79,7 +79,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - /** * The methods declared by the ASNetworkImageNodeDelegate protocol allow the adopting delegate to respond to - * notifications such as fininished decoding and downloading an image. + * notifications such as finished decoding and downloading an image. */ @protocol ASNetworkImageNodeDelegate diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 5071c87193..6a8e37cbdb 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -14,11 +14,15 @@ #import "ASEqualityHelpers.h" #import "ASThread.h" #import "ASInternalHelpers.h" +#import "ASImageContainerProtocolCategories.h" +#import "ASImageNode+AnimatedImage.h" #if PIN_REMOTE_IMAGE #import "ASPINRemoteImageDownloader.h" #endif +static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; + @interface ASNetworkImageNode () { ASDN::RecursiveMutex _lock; @@ -44,9 +48,11 @@ BOOL _downloaderSupportsNewProtocol; BOOL _downloaderImplementsSetProgress; BOOL _downloaderImplementsSetPriority; + BOOL _downloaderImplementsAnimatedImage; BOOL _cacheSupportsNewProtocol; BOOL _cacheSupportsClearing; + BOOL _cacheSupportsSynchronousFetch; } @end @@ -68,9 +74,11 @@ _downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; _downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; + _downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)]; _cacheSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)]; _cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; + _cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)]; _shouldCacheImage = YES; self.shouldBypassEnsureDisplay = YES; @@ -128,15 +136,22 @@ - (void)setDefaultImage:(UIImage *)defaultImage { - ASDN::MutexLocker l(_lock); + _lock.lock(); if (ASObjectIsEqual(defaultImage, _defaultImage)) { + _lock.unlock(); return; } _defaultImage = defaultImage; if (!_imageLoaded) { - self.image = _defaultImage; + _lock.unlock(); + // Locking: it is important to release _lock before entering setImage:, as it needs to release the lock before -invalidateCalculatedLayout. + // If we continue to hold the lock here, it will still be locked until the next unlock() call, causing a possible deadlock with + // -[ASNetworkImageNode displayWillStart] (which is called on a different thread / main, at an unpredictable time due to ASMainRunloopQueue). + self.image = defaultImage; + } else { + _lock.unlock(); } } @@ -162,11 +177,28 @@ return _delegate; } +- (BOOL)placeholderShouldPersist +{ + ASDN::MutexLocker l(_lock); + return (self.image == nil && _URL != nil); +} + /* displayWillStart in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary in ASMultiplexImageNode as well. */ - (void)displayWillStart { [super displayWillStart]; + + if (_cacheSupportsSynchronousFetch) { + ASDN::MutexLocker l(_lock); + if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) { + UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image]; + if (result) { + self.image = result; + _imageLoaded = YES; + } + } + } [self fetchData]; @@ -182,6 +214,8 @@ in ASMultiplexImageNode as well. */ - (void)visibilityDidChange:(BOOL)isVisible { + [super visibilityDidChange:isVisible]; + if (_downloaderImplementsSetPriority) { ASDN::MutexLocker l(_lock); if (_downloadIdentifier != nil) { @@ -228,8 +262,7 @@ ASDN::MutexLocker l(_lock); [self _cancelImageDownload]; - self.image = _defaultImage; - _imageLoaded = NO; + [self _clearImage]; if (_cacheSupportsClearing) { [_cache clearFetchedImageFromCacheWithURL:_URL]; } @@ -248,6 +281,25 @@ #pragma mark - Private methods -- only call with lock. +- (void)_clearImage +{ + // Destruction of bigger images on the main thread can be expensive + // and can take some time, so we dispatch onto a bg queue to + // actually dealloc. + __block UIImage *image = self.image; + CGSize imageSize = image.size; + BOOL shouldReleaseImageOnBackgroundThread = imageSize.width > kMinReleaseImageOnBackgroundSize.width || + imageSize.height > kMinReleaseImageOnBackgroundSize.height; + if (shouldReleaseImageOnBackgroundThread) { + ASPerformBlockOnBackgroundThread(^{ + image = nil; + }); + } + self.animatedImage = nil; + self.image = _defaultImage; + _imageLoaded = NO; +} + - (void)_cancelImageDownload { if (!_downloadIdentifier) { @@ -262,7 +314,7 @@ _cacheUUID = nil; } -- (void)_downloadImageWithCompletion:(void (^)(UIImage *image, NSError*, id downloadIdentifier))finished +- (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier))finished { ASPerformBlockOnBackgroundThread(^{ ASDN::MutexLocker l(_lock); @@ -270,12 +322,14 @@ _downloadIdentifier = [_downloader downloadImageWithURL:_URL callbackQueue:dispatch_get_main_queue() downloadProgress:NULL - completion:^(UIImage * _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) { + completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { if (finished != NULL) { - finished(image, error, downloadIdentifier); + finished(imageContainer, error, downloadIdentifier); } }]; } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" _downloadIdentifier = [_downloader downloadImageWithURL:_URL callbackQueue:dispatch_get_main_queue() downloadProgressBlock:NULL @@ -284,12 +338,14 @@ finished([UIImage imageWithCGImage:responseImage], error, nil); } }]; +#pragma clang diagnostic pop } }); } - (void)_lazilyLoadImageIfNecessary { + // FIXME: We should revisit locking in this method (e.g. to access the instance variables at the top, and holding lock while calling delegate) if (!_imageLoaded && _URL != nil && _downloadIdentifier == nil) { { ASDN::MutexLocker l(_lock); @@ -325,7 +381,7 @@ } } else { __weak __typeof__(self) weakSelf = self; - void (^finished)(UIImage *, NSError *, id downloadIdentifier) = ^(UIImage *responseImage, NSError *error, id downloadIdentifier) { + void (^finished)(id , NSError *, id downloadIdentifier) = ^(id imageContainer, NSError *error, id downloadIdentifier) { __typeof__(self) strongSelf = weakSelf; if (strongSelf == nil) { return; @@ -339,9 +395,13 @@ return; } - if (responseImage != NULL) { + if (imageContainer != nil) { strongSelf->_imageLoaded = YES; - strongSelf.image = responseImage; + if ([imageContainer asdk_animatedImageData] && _downloaderImplementsAnimatedImage) { + strongSelf.animatedImage = [_downloader animatedImageWithData:[imageContainer asdk_animatedImageData]]; + } else { + strongSelf.image = [imageContainer asdk_image]; + } } strongSelf->_downloadIdentifier = nil; @@ -351,7 +411,7 @@ { ASDN::MutexLocker l(strongSelf->_lock); - if (responseImage != NULL) { + if (imageContainer != nil) { [strongSelf->_delegate imageNode:strongSelf didLoadImage:strongSelf.image]; } else if (error && _delegateSupportsDidFailWithError) { @@ -364,16 +424,16 @@ NSUUID *cacheUUID = [NSUUID UUID]; _cacheUUID = cacheUUID; - void (^cacheCompletion)(UIImage *) = ^(UIImage *image) { + void (^cacheCompletion)(id ) = ^(id imageContainer) { // If the cache UUID changed, that means this request was cancelled. if (![_cacheUUID isEqual:cacheUUID]) { return; } - if (image == NULL && _downloader != nil) { + if ([imageContainer asdk_image] == NULL && _downloader != nil) { [self _downloadImageWithCompletion:finished]; } else { - finished(image, NULL, nil); + finished(imageContainer, NULL, nil); } }; @@ -382,11 +442,14 @@ callbackQueue:dispatch_get_main_queue() completion:cacheCompletion]; } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [_cache fetchCachedImageWithURL:_URL callbackQueue:dispatch_get_main_queue() completion:^(CGImageRef image) { cacheCompletion([UIImage imageWithCGImage:image]); }]; +#pragma clang diagnostic pop } } else { [self _downloadImageWithCompletion:finished]; diff --git a/AsyncDisplayKit/ASPagerFlowLayout.m b/AsyncDisplayKit/ASPagerFlowLayout.m index 2118a394f9..dcb89a4008 100644 --- a/AsyncDisplayKit/ASPagerFlowLayout.m +++ b/AsyncDisplayKit/ASPagerFlowLayout.m @@ -34,7 +34,10 @@ - (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset { - UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:self.currentIndexPath]; + if ([self _dataSourceIsEmpty]) { + return proposedContentOffset; + } + UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; CGFloat xOffset = (self.collectionView.bounds.size.width - attributes.frame.size.width) / 2; return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y); } @@ -52,6 +55,11 @@ return nil; } +- (BOOL)_dataSourceIsEmpty +{ + return ([self.collectionView numberOfSections] == 0 || [self.collectionView numberOfItemsInSection:0] == 0); +} + - (CGRect)_visibleRect { CGRect visibleRect; @@ -60,4 +68,4 @@ return visibleRect; } -@end \ No newline at end of file +@end diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index ea237ef5a1..7b64836928 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -55,7 +55,7 @@ /** * Provides the constrained size range for measuring the node at the index path. * - * @param collectionView The sender. + * @param pagerNode The sender. * * @param indexPath The index path of the node. * diff --git a/AsyncDisplayKit/ASRunLoopQueue.h b/AsyncDisplayKit/ASRunLoopQueue.h new file mode 100644 index 0000000000..7335528a2c --- /dev/null +++ b/AsyncDisplayKit/ASRunLoopQueue.h @@ -0,0 +1,23 @@ +// +// ASRunLoopQueue.h +// AsyncDisplayKit +// +// Created by Rahul Malik on 3/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASRunLoopQueue : NSObject + +- (instancetype)initWithRunLoop:(CFRunLoopRef)runloop andHandler:(void(^)(ObjectType dequeuedItem, BOOL isQueueDrained))handlerBlock; + +- (void)enqueue:(ObjectType)object; + +@property (nonatomic, assign) NSUInteger batchSize; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/ASRunLoopQueue.mm b/AsyncDisplayKit/ASRunLoopQueue.mm new file mode 100644 index 0000000000..a18f7a8a70 --- /dev/null +++ b/AsyncDisplayKit/ASRunLoopQueue.mm @@ -0,0 +1,162 @@ +// +// ASRunLoopQueue.m +// AsyncDisplayKit +// +// Created by Rahul Malik on 3/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASRunLoopQueue.h" +#import "ASThread.h" + +#import +#import + +#define ASRunLoopQueueLoggingEnabled 0 + +static void runLoopSourceCallback(void *info) { + // No-op +#if ASRunLoopQueueLoggingEnabled + NSLog(@"<%@> - Called runLoopSourceCallback", info); +#endif +} + +@interface ASRunLoopQueue () { + CFRunLoopRef _runLoop; + CFRunLoopObserverRef _runLoopObserver; + CFRunLoopSourceRef _runLoopSource; + std::deque _internalQueue; + ASDN::RecursiveMutex _internalQueueLock; + +#if ASRunLoopQueueLoggingEnabled + NSTimer *_runloopQueueLoggingTimer; +#endif +} + +@property (nonatomic, copy) void (^queueConsumer)(id dequeuedItem, BOOL isQueueDrained); + +@end + +@implementation ASRunLoopQueue + +- (instancetype)initWithRunLoop:(CFRunLoopRef)runloop andHandler:(void(^)(id dequeuedItem, BOOL isQueueDrained))handlerBlock +{ + if (self = [super init]) { + _runLoop = runloop; + _internalQueue = std::deque(); + _queueConsumer = [handlerBlock copy]; + _batchSize = 1; + void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { + [self processQueue]; + }; + _runLoopObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, handlerBlock); + CFRunLoopAddObserver(_runLoop, _runLoopObserver, kCFRunLoopCommonModes); + + // It is not guaranteed that the runloop will turn if it has no scheduled work, and this causes processing of + // the queue to stop. Attaching a custom loop source to the run loop and signal it if new work needs to be done + CFRunLoopSourceContext *runLoopSourceContext = (CFRunLoopSourceContext *)calloc(1, sizeof(CFRunLoopSourceContext)); + runLoopSourceContext->perform = runLoopSourceCallback; +#if ASRunLoopQueueLoggingEnabled + runLoopSourceContext->info = (__bridge void *)self; +#endif + _runLoopSource = CFRunLoopSourceCreate(NULL, 0, runLoopSourceContext); + CFRunLoopAddSource(runloop, _runLoopSource, kCFRunLoopCommonModes); + free(runLoopSourceContext); + +#if ASRunLoopQueueLoggingEnabled + _runloopQueueLoggingTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(checkRunLoop) userInfo:nil repeats:YES]; + [[NSRunLoop mainRunLoop] addTimer:_runloopQueueLoggingTimer forMode:NSRunLoopCommonModes]; +#endif + } + return self; +} + +- (void)dealloc +{ + if (CFRunLoopContainsSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes)) { + CFRunLoopRemoveSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes); + } + CFRelease(_runLoopSource); + _runLoopSource = nil; + + if (CFRunLoopObserverIsValid(_runLoopObserver)) { + CFRunLoopObserverInvalidate(_runLoopObserver); + } + CFRelease(_runLoopObserver); + _runLoopObserver = nil; +} + +#if ASRunLoopQueueLoggingEnabled +- (void)checkRunLoop +{ + NSLog(@"<%@> - Jobs: %ld", self, _internalQueue.size()); +} +#endif + +- (void)processQueue +{ + std::deque itemsToProcess = std::deque(); + BOOL isQueueDrained = NO; + { + ASDN::MutexLocker l(_internalQueueLock); + + // Early-exit if the queue is empty. + if (_internalQueue.empty()) { + return; + } + + // Snatch the next batch of items. + NSUInteger totalNodeCount = _internalQueue.size(); + for (int i = 0; i < MIN(self.batchSize, totalNodeCount); i++) { + id node = _internalQueue[0]; + itemsToProcess.push_back(node); + _internalQueue.pop_front(); + } + + if (_internalQueue.empty()) { + isQueueDrained = YES; + } + } + + unsigned long numberOfItems = itemsToProcess.size(); + for (int i = 0; i < numberOfItems; i++) { + if (isQueueDrained && i == numberOfItems - 1) { + self.queueConsumer(itemsToProcess[i], YES); + } else { + self.queueConsumer(itemsToProcess[i], isQueueDrained); + } + } + + // If the queue is not fully drained yet force another run loop to process next batch of items + if (!isQueueDrained) { + CFRunLoopSourceSignal(_runLoopSource); + CFRunLoopWakeUp(_runLoop); + } +} + +- (void)enqueue:(id)object +{ + if (!object) { + return; + } + + ASDN::MutexLocker l(_internalQueueLock); + + // Check if the object exists. + BOOL foundObject = NO; + for (id currentObject : _internalQueue) { + if (currentObject == object) { + foundObject = YES; + break; + } + } + + if (!foundObject) { + _internalQueue.push_back(object); + + CFRunLoopSourceSignal(_runLoopSource); + CFRunLoopWakeUp(_runLoop); + } +} + +@end diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index a54f0bf11e..366209bf15 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -9,7 +9,7 @@ #import "ASFlowLayoutController.h" #import "ASTableViewInternal.h" #import "ASDisplayNode+Subclasses.h" -#import "ASRangeController.h" +#import "ASRangeControllerUpdateRangeProtocol+Beta.h" @interface _ASTablePendingState : NSObject @property (weak, nonatomic) id delegate; @@ -77,6 +77,15 @@ } } +- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode +{ + if (!self.isNodeLoaded) { + return; + } + + [self.view.rangeController updateCurrentRangeWithMode:rangeMode]; +} + - (_ASTablePendingState *)pendingState { if (!_pendingState && ![self isNodeLoaded]) { @@ -129,7 +138,7 @@ return (ASTableView *)[super view]; } -#if RangeControllerLoggingEnabled +#if ASRangeControllerLoggingEnabled - (void)visibilityDidChange:(BOOL)isVisible { [super visibilityDidChange:isVisible]; diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index c2c50cab4a..fe1ac2a193 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -81,7 +81,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Tuning parameters for a range type in the specified mode. * - * @param rangeMode The range mode to get the runing parameters for. + * @param rangeMode The range mode to get the running parameters for. * @param rangeType The range type to get the tuning parameters for. * * @returns A tuning parameter value for the given range type in the given mode. @@ -92,10 +92,10 @@ NS_ASSUME_NONNULL_BEGIN - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; /** - * Set the tuning parameters for a range type in the specigied mode. + * Set the tuning parameters for a range type in the specified mode. * * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeMode The range mode to set the runing parameters for. + * @param rangeMode The range mode to set the running parameters for. * @param rangeType The range type to set the tuning parameters for. * * @see ASLayoutRangeMode @@ -143,7 +143,7 @@ NS_ASSUME_NONNULL_BEGIN * Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view, with animation enabled and no completion block. * You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations * to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating - * the operations simultaneously. This method is must be called from the main thread. It's important to remeber that the ASTableView will + * the operations simultaneously. This method is must be called from the main thread. It's important to remember that the ASTableView will * be processing the updates asynchronously after this call is completed. */ - (void)endUpdates; @@ -152,7 +152,7 @@ NS_ASSUME_NONNULL_BEGIN * Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view. * You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations * to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating - * the operations simultaneously. This method is must be called from the main thread. It's important to remeber that the ASTableView will + * the operations simultaneously. This method is must be called from the main thread. It's important to remember that the ASTableView will * be processing the updates asynchronously after this call and are not guaranteed to be reflected in the ASTableView until * the completion block is executed. * @@ -163,6 +163,11 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL completed))completion; +/** + * Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. + */ +- (void)waitUntilAllUpdatesAreCommitted; + /** * Inserts one or more sections, with an option to animate the insertion. * @@ -346,7 +351,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Indicator to lock the data source for data fetching in async mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception + * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception * due to the data access in async mode. * * @param tableView The sender. @@ -355,7 +360,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Indicator to unlock the data source for data fetching in asyn mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception + * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception * due to the data access in async mode. * * @param tableView The sender. diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index c6eefd394b..6718932297 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -13,12 +13,14 @@ #import "ASCellNode+Internal.h" #import "ASChangeSetDataController.h" #import "ASDelegateProxy.h" +#import "ASDisplayNodeExtras.h" #import "ASDisplayNode+Beta.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASInternalHelpers.h" #import "ASLayout.h" #import "ASLayoutController.h" #import "ASRangeController.h" +#import "ASRangeControllerUpdateRangeProtocol+Beta.h" #import "_ASDisplayLayer.h" #import @@ -86,9 +88,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (instancetype)_initWithTableView:(ASTableView *)tableView; @end -@interface ASTableView () +@interface ASTableView () { ASTableViewProxy *_proxyDataSource; ASTableViewProxy *_proxyDelegate; @@ -118,13 +118,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } @property (atomic, assign) BOOL asyncDataSourceLocked; -@property (nonatomic, retain, readwrite) ASDataController *dataController; +@property (nonatomic, strong, readwrite) ASDataController *dataController; // Used only when ASTableView is created directly rather than through ASTableNode. // We create a node so that logic related to appearance, memory management, etc can be located there // for both the node-based and view-based version of the table. // This also permits sharing logic with ASCollectionNode, as the superclass is not UIKit-controlled. -@property (nonatomic, retain) ASTableNode *strongTableNode; +@property (nonatomic, strong) ASTableNode *strongTableNode; // Always set, whether ASCollectionView is created directly or via ASCollectionNode. @property (nonatomic, weak) ASTableNode *tableNode; @@ -287,8 +287,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (asyncDelegate == nil) { _asyncDelegate = nil; _proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; + _asyncDelegateImplementsScrollviewDidScroll = NO; } else { _asyncDelegate = asyncDelegate; + _asyncDelegateImplementsScrollviewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; } @@ -392,6 +394,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_dataController endUpdatesAnimated:animated completion:completion]; } +- (void)waitUntilAllUpdatesAreCommitted +{ + ASDisplayNodeAssertMainThread(); + [_dataController waitUntilAllUpdatesAreCommitted]; +} + - (void)layoutSubviews { if (_nodesConstrainedWidth != self.bounds.size.width) { @@ -485,7 +493,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } - (void)adjustContentOffsetWithNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths inserting:(BOOL)inserting { - // Maintain the users visible window when inserting or deleteing cells by adjusting the content offset for nodes + // Maintain the users visible window when inserting or deleting cells by adjusting the content offset for nodes // before the visible area. If in a begin/end updates block this will update _contentOffsetAdjustment, otherwise it will // update self.contentOffset directly. @@ -493,7 +501,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; CGFloat dir = (inserting) ? +1 : -1; CGFloat adjustment = 0; - NSIndexPath *top = _contentOffsetAdjustmentTopVisibleRow ?: self.indexPathsForVisibleRows.firstObject; + NSIndexPath *top = _contentOffsetAdjustmentTopVisibleRow ? : self.indexPathsForVisibleRows.firstObject; for (int index = 0; index < indexPaths.count; index++) { NSIndexPath *indexPath = indexPaths[index]; @@ -514,8 +522,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -#pragma mark - -#pragma mark Intercepted selectors + +#pragma mark - Intercepted selectors - (void)setTableHeaderView:(UIView *)tableHeaderView { @@ -569,58 +577,26 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return [_dataController numberOfRowsInSection:section]; } -- (ASScrollDirection)scrollDirection -{ - CGPoint scrollVelocity; - if (self.isTracking) { - scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; - } else { - scrollVelocity = _deceleratingVelocity; - } - ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; - return ASScrollDirectionApplyTransform(scrollDirection, self.transform); -} - -- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)velocity -{ - ASScrollDirection direction = ASScrollDirectionNone; - if (velocity.y > 0) { - direction = ASScrollDirectionDown; - } else if (velocity.y < 0) { - direction = ASScrollDirectionUp; - } - return direction; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) { - ASCellNode *node = [tableCell node]; - [node visibleNodeDidScroll:scrollView withCellFrame:tableCell.frame]; - } - if (_asyncDelegateImplementsScrollviewDidScroll) { - [_asyncDelegate scrollViewDidScroll:scrollView]; - } -} - - (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { _pendingVisibleIndexPath = indexPath; - - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + + ASCellNode *cellNode = [cell node]; + cellNode.scrollView = tableView; if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) { [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; } + + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; - ASCellNode *cellNode = [cell node]; - - if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(visibleNodeDidScroll:withCellFrame:))) { - [_cellsForVisibilityUpdates addObject:cell]; - } if (cellNode.neverShowPlaceholders) { [cellNode recursivelyEnsureDisplaySynchronously:YES]; } + + if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:))) { + [_cellsForVisibilityUpdates addObject:cell]; + } } - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath @@ -628,16 +604,19 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if ([_pendingVisibleIndexPath isEqual:indexPath]) { _pendingVisibleIndexPath = nil; } + + ASCellNode *cellNode = [cell node]; - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]) { - ASCellNode *node = ((_ASTableViewCell *)cell).node; - ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil."); - [_asyncDelegate tableView:self didEndDisplayingNode:node forRowAtIndexPath:indexPath]; + ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); + [_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath]; } - [_cellsForVisibilityUpdates removeObject:cell]; + if ([_cellsForVisibilityUpdates containsObject:cell]) { + [_cellsForVisibilityUpdates removeObject:cell]; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -645,11 +624,28 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_asyncDelegate tableView:self didEndDisplayingNodeForRowAtIndexPath:indexPath]; } #pragma clang diagnostic pop + + cellNode.scrollView = nil; } -#pragma mark - -#pragma mark Batch Fetching +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + // If a scroll happenes the current range mode needs to go to full + ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; + if (ASInterfaceStateIncludesVisible(interfaceState)) { + [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; + } + + for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) { + [[tableCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged + inScrollView:scrollView + withCellFrame:tableCell.frame]; + } + if (_asyncDelegateImplementsScrollviewDidScroll) { + [_asyncDelegate scrollViewDidScroll:scrollView]; + } +} - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { @@ -659,7 +655,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ); if (targetContentOffset != NULL) { - [self handleBatchFetchScrollingToOffset:*targetContentOffset]; + ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { @@ -667,7 +664,62 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (BOOL)shouldBatchFetch + +#pragma mark - Scroll Direction + +- (ASScrollDirection)scrollDirection +{ + CGPoint scrollVelocity; + if (self.isTracking) { + scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; + } else { + scrollVelocity = _deceleratingVelocity; + } + + ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; + return ASScrollDirectionApplyTransform(scrollDirection, self.transform); +} + +- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity +{ + ASScrollDirection direction = ASScrollDirectionNone; + ASScrollDirection scrollableDirections = [self scrollableDirections]; + + if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. + if (scrollVelocity.y < 0.0) { + direction |= ASScrollDirectionDown; + } else if (scrollVelocity.y > 0.0) { + direction |= ASScrollDirectionUp; + } + } + + return direction; +} + +- (ASScrollDirection)scrollableDirections +{ + ASScrollDirection scrollableDirection = ASScrollDirectionNone; + CGFloat totalContentWidth = self.contentSize.width + self.contentInset.left + self.contentInset.right; + CGFloat totalContentHeight = self.contentSize.height + self.contentInset.top + self.contentInset.bottom; + + if (self.alwaysBounceHorizontal || totalContentWidth > self.bounds.size.width) { // Can scroll horizontally. + scrollableDirection |= ASScrollDirectionHorizontalDirections; + } + if (self.alwaysBounceVertical || totalContentHeight > self.bounds.size.height) { // Can scroll vertically. + scrollableDirection |= ASScrollDirectionVerticalDirections; + } + return scrollableDirection; +} + + +#pragma mark - Batch Fetching + +- (ASBatchContext *)batchContext +{ + return _batchContext; +} + +- (BOOL)canBatchFetch { // if the delegate does not respond to this method, there is no point in starting to fetch BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]; @@ -678,16 +730,40 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +- (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - - if (![self shouldBatchFetch]) { + // Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided + if (changes == 0) { return; } - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { - [_batchContext beginBatchFetching]; + // Push this to the next runloop to be sure the scroll view has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); +} + +- (void)_checkForBatchFetching +{ + // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: + if (self.isDragging || self.isTracking) { + return; + } + + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; +} + +- (void)_beginBatchFetchingIfNeededWithScrollView:(UIScrollView *)scrollView forScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset +{ + if (ASDisplayShouldFetchBatchForScrollView(self, scrollDirection, contentOffset)) { + [self _beginBatchFetching]; + } +} + +- (void)_beginBatchFetching +{ + [_batchContext beginBatchFetching]; + if ([_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; }); @@ -696,10 +772,21 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - ASRangeControllerDataSource +- (ASRangeController *)rangeController +{ + return _rangeController; +} + - (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController { ASDisplayNodeAssertMainThread(); + // Calling indexPathsForVisibleRows will trigger UIKit to call reloadData if it never has, which can result + // in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast. + if (CGRectEqualToRect(self.bounds, CGRectZero)) { + return @[]; + } + NSArray *visibleIndexPaths = self.indexPathsForVisibleRows; if (_pendingVisibleIndexPath) { @@ -764,15 +851,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController { - ASTableNode *tableNode = self.tableNode; - if (tableNode) { - return self.tableNode.interfaceState; - } else { - // Until we can always create an associated ASTableNode without a retain cycle, - // we might be on our own to try to guess if we're visible. The node normally - // handles this even if it is the root / directly added to the view hierarchy. - return (self.window != nil ? ASInterfaceStateVisible : ASInterfaceStateNone); - } + return ASInterfaceStateForDisplayNode(self.tableNode, self.window); } #pragma mark - ASRangeControllerDelegate @@ -830,6 +909,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; }); if (_automaticallyAdjustsContentOffset) { @@ -849,6 +929,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; }); if (_automaticallyAdjustsContentOffset) { @@ -869,6 +950,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; }); } @@ -884,6 +966,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; }); } @@ -1019,20 +1102,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)clearContents { - for (NSArray *section in [_dataController completedNodes]) { - for (ASDisplayNode *node in section) { - [node recursivelyClearContents]; - } - } + [_rangeController clearContents]; } - (void)clearFetchedData { - for (NSArray *section in [_dataController completedNodes]) { - for (ASDisplayNode *node in section) { - [node recursivelyClearFetchedData]; - } - } + [_rangeController clearFetchedData]; } #pragma mark - _ASDisplayView behavior substitutions @@ -1054,6 +1129,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (!visible && node.inHierarchy) { [node __exitHierarchy]; } + + // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their + // their update in the layout pass + if (![node supportsRangeManagedInterfaceState]) { + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; + } } @end diff --git a/AsyncDisplayKit/ASTableViewInternal.h b/AsyncDisplayKit/ASTableViewInternal.h index cc93c44a84..3c1eb2f0c0 100644 --- a/AsyncDisplayKit/ASTableViewInternal.h +++ b/AsyncDisplayKit/ASTableViewInternal.h @@ -12,8 +12,9 @@ @interface ASTableView (Internal) -@property (nonatomic, retain, readonly) ASDataController *dataController; +@property (nonatomic, strong, readonly) ASDataController *dataController; @property (nonatomic, weak, readwrite) ASTableNode *tableNode; +@property (nonatomic, strong, readonly) ASRangeController *rangeController; /** * Initializer. @@ -25,7 +26,7 @@ * * @param dataControllerClass A controller class injected to and used to create a data controller for the table view. * - * @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op. + * @param ownedByNode Indicates whether the tableView is owned by an ASTableNode. */ - (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass ownedByNode:(BOOL)ownedByNode; diff --git a/AsyncDisplayKit/ASTextNode+Beta.h b/AsyncDisplayKit/ASTextNode+Beta.h index b4d3b88982..564f5d1917 100644 --- a/AsyncDisplayKit/ASTextNode+Beta.h +++ b/AsyncDisplayKit/ASTextNode+Beta.h @@ -6,6 +6,7 @@ // Copyright © 2016 Facebook. All rights reserved. // +NS_ASSUME_NONNULL_BEGIN @interface ASTextNode () @@ -13,11 +14,19 @@ @abstract An array of descending scale factors that will be applied to this text node to try to make it fit within its constrained size @default nil (no scaling) */ -@property (nonatomic, copy) NSArray *pointSizeScaleFactors; +@property (nullable, nonatomic, copy) NSArray *pointSizeScaleFactors; + +#pragma mark - ASTextKit Customization +/** + A block to provide a hook to provide a custom NSLayoutManager to the ASTextKitRenderer + */ +@property (nullable, nonatomic, copy) NSLayoutManager * (^layoutManagerCreationBlock)(void); /** - @abstract The currently applied scale factor, or 0 if the text node is not being scaled. + A block to provide a hook to provide a NSTextStorage to the TextKit's layout manager. */ -@property (nonatomic, assign, readonly) CGFloat currentScaleFactor; +@property (nullable, nonatomic, copy) NSTextStorage * (^textStorageCreationBlock)(NSAttributedString *_Nullable attributedString); -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASTextNode.h b/AsyncDisplayKit/ASTextNode.h index a1767edf63..f96bf51370 100644 --- a/AsyncDisplayKit/ASTextNode.h +++ b/AsyncDisplayKit/ASTextNode.h @@ -213,17 +213,6 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { */ @property (nonatomic, assign) BOOL passthroughNonlinkTouches; -#pragma mark - ASTextKit Customization -/** - A block to provide a hook to provide a custom NSLayoutManager to the ASTextKitRenderer - */ -@property (nonatomic, copy) NSLayoutManager * (^layoutManagerCreationBlock)(void); - -/** - A block to provide a hook to provide a NSTextStorage to the Text Kit's layout manager. - */ -@property (nonatomic, copy) NSTextStorage * (^textStorageCreationBlock)(NSAttributedString *attributedString); - @end /** diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 3cc4e7ccbd..b3771ba14d 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -17,7 +17,7 @@ #import #import "ASTextKitCoreTextAdditions.h" -#import "ASTextKitHelpers.h" +#import "ASTextKitComponents.h" #import "ASTextKitFontSizeAdjuster.h" #import "ASTextKitRenderer.h" #import "ASTextKitRenderer+Positioning.h" @@ -218,10 +218,9 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; #pragma mark - Renderer Management -//only safe to call on the main thread because self.bounds is only safe to call on the main thread one our node is loaded - (ASTextKitRenderer *)_renderer { - return [self _rendererWithBounds:self.bounds]; + return [self _rendererWithBounds:self.threadSafeBounds]; } - (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds @@ -244,7 +243,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; .maximumNumberOfLines = _maximumNumberOfLines, .exclusionPaths = _exclusionPaths, .pointSizeScaleFactors = _pointSizeScaleFactors, - .currentScaleFactor = self.currentScaleFactor, .layoutManagerCreationBlock = self.layoutManagerCreationBlock, .textStorageCreationBlock = self.textStorageCreationBlock, }; @@ -269,7 +267,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)_invalidateRendererIfNeeded { - [self _invalidateRendererIfNeededForBoundsSize:self.bounds.size]; + [self _invalidateRendererIfNeededForBoundsSize:self.threadSafeBounds.size]; } - (void)_invalidateRendererIfNeededForBoundsSize:(CGSize)boundsSize @@ -319,8 +317,11 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)calculatedLayoutDidChange { + [super calculatedLayoutDidChange]; + ASLayout *layout = self.calculatedLayout; if (layout != nil) { + _constrainedSize = layout.size; _renderer.constrainedSize = layout.size; } } @@ -339,8 +340,16 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; [self setNeedsDisplay]; CGSize size = [[self _renderer] size]; - // the renderer computes the current scale factor during sizing, so let's grab it here - _currentScaleFactor = _renderer.currentScaleFactor; + if (self.attributedString.length > 0) { + CGFloat screenScale = ASScreenScale(); + self.ascender = round([[_attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale; + self.descender = round([[_attributedString attribute:NSFontAttributeName atIndex:_attributedString.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale; + if (_renderer.currentScaleFactor > 0 && _renderer.currentScaleFactor < 1.0) { + // while not perfect, this is a good estimate of what the ascender of the scaled font will be. + self.ascender *= _renderer.currentScaleFactor; + self.descender *= _renderer.currentScaleFactor; + } + } return size; } @@ -357,6 +366,12 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } _attributedString = ASCleanseAttributedStringOfCoreTextAttributes(attributedString); + + if (_attributedString.length > 0) { + CGFloat screenScale = ASScreenScale(); + self.ascender = round([[_attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale; + self.descender = round([[_attributedString attribute:NSFontAttributeName atIndex:_attributedString.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale; + } // Sync the truncation string with attributes from the updated _attributedString // Without this, the size calculation of the text with truncation applied will @@ -373,20 +388,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; self.accessibilityLabel = _attributedString.string; - if (_attributedString.length == 0) { - // We're not an accessibility element by default if there is no string. - self.isAccessibilityElement = NO; - } else { - self.isAccessibilityElement = YES; - } - - // reset the scale factor if we get a new string. - _currentScaleFactor = 0; - if (attributedString.length > 0) { - CGFloat screenScale = ASScreenScale(); - self.ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale; - self.descender = round([[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale; - } + // We're an accessibility element by default if there is a string. + self.isAccessibilityElement = _attributedString.length != 0; } #pragma mark - Text Layout @@ -443,7 +446,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { - return [[ASTextNodeDrawParameters alloc] initWithBounds:self.bounds backgroundColor:self.backgroundColor]; + return [[ASTextNodeDrawParameters alloc] initWithBounds:self.threadSafeBounds backgroundColor:self.backgroundColor]; } #pragma mark - Attributes @@ -476,7 +479,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; // Final output vars __block id linkAttributeValue = nil; - __block NSString *linkAttributeName = nil; __block BOOL inTruncationMessage = NO; [renderer enumerateTextIndexesAtPosition:point usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) { @@ -545,7 +547,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; // Set the values for the next iteration linkAttributeValue = value; - linkAttributeName = name; break; } @@ -579,7 +580,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; if (([self _pendingLinkTap] || [self _pendingTruncationTap]) && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] - && CGRectContainsPoint(self.view.bounds, [gestureRecognizer locationInView:self.view])) { + && CGRectContainsPoint(self.threadSafeBounds, [gestureRecognizer locationInView:self.view])) { return NO; } @@ -634,7 +635,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"]; fadeOut.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - fadeOut.fromValue = possibleFadeIn.toValue ?: @(((CALayer *)weakHighlightLayer.presentationLayer).opacity); + fadeOut.fromValue = possibleFadeIn.toValue ? : @(((CALayer *)weakHighlightLayer.presentationLayer).opacity); fadeOut.toValue = @0.0; fadeOut.fillMode = kCAFillModeBoth; fadeOut.duration = ASTextNodeHighlightFadeOutDuration; @@ -897,11 +898,16 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; { [super touchesMoved:touches withEvent:event]; + UITouch *touch = [touches anyObject]; + CGPoint locationInView = [touch locationInView:self.view]; + // on 3D Touch enabled phones, this gets fired with changes in force, and usually will get fired immediately after touchesBegan:withEvent: + if (CGPointEqualToPoint([touch previousLocationInView:self.view], locationInView)) + return; + // If touch has moved out of the current highlight range, clear the highlight. if (_highlightRange.length > 0) { NSRange range = NSMakeRange(0, 0); - CGPoint point = [[touches anyObject] locationInView:self.view]; - [self _linkAttributeValueAtPoint:point + [self _linkAttributeValueAtPoint:locationInView attributeName:NULL range:&range inAdditionalTruncationMessage:NULL @@ -995,7 +1001,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } } -//only safe to call on main thread, because [self _renderer] is only safe to call on the main thread - (UIEdgeInsets)shadowPadding { return [self shadowPaddingWithRenderer:[self _renderer]]; @@ -1117,6 +1122,10 @@ static NSAttributedString *DefaultTruncationAttributedString() */ - (NSAttributedString *)_composedTruncationString { + //If we have neither return the default + if (!_additionalTruncationMessage && !_truncationAttributedString) { + return _composedTruncationString; + } // Short circuit if we only have one or the other. if (!_additionalTruncationMessage) { return _truncationAttributedString; diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index f9ad8770d3..663ac2def6 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -6,41 +6,64 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import #if TARGET_OS_IOS +#import + +@class AVAsset, AVPlayer, AVPlayerItem; @protocol ASVideoNodeDelegate; -// This is a relatively new component of AsyncDisplayKit. It has many useful features, but -// there is room for further expansion and optimization. Please report any issues or requests -// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues +NS_ASSUME_NONNULL_BEGIN + +// IMPORTANT NOTES: +// 1. Applications using ASVideoNode must link AVFoundation! (this provides the AV* classes below) +// 2. This is a relatively new component of AsyncDisplayKit. It has many useful features, but +// there is room for further expansion and optimization. Please report any issues or requests +// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues @interface ASVideoNode : ASControlNode -@property (atomic, strong, readwrite) AVAsset *asset; -@property (atomic, strong, readonly) AVPlayer *player; -@property (atomic, strong, readonly) AVPlayerItem *currentItem; -// When autoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. -// If it leaves the visible interfaceState it will pause but will resume once it has returned +- (void)play; +- (void)pause; +- (BOOL)isPlaying; + +@property (nullable, atomic, strong, readwrite) AVAsset *asset; + +@property (nullable, atomic, strong, readonly) AVPlayer *player; +@property (nullable, atomic, strong, readonly) AVPlayerItem *currentItem; + +/** + * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. + * If it leaves the visible interfaceState it will pause but will resume once it has returned. + */ @property (nonatomic, assign, readwrite) BOOL shouldAutoplay; @property (nonatomic, assign, readwrite) BOOL shouldAutorepeat; @property (nonatomic, assign, readwrite) BOOL muted; +//! Defaults to AVLayerVideoGravityResizeAspect @property (atomic) NSString *gravity; -@property (atomic) ASButtonNode *playButton; -@property (atomic, weak, readwrite) id delegate; +//! Defaults to an ASDefaultPlayButton instance. +@property (nullable, atomic) ASButtonNode *playButton; -- (void)play; -- (void)pause; - -- (BOOL)isPlaying; +@property (nullable, atomic, weak, readwrite) id delegate; @end @protocol ASVideoNodeDelegate @optional +/** + * @abstract Delegate method invoked when the node's video has played to its end time. + * @param videoNode The video node has played to its end time. + */ - (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode; +/** + * @abstract Delegate method invoked the node is tapped. + * @param videoNode The video node that was tapped. + * @discussion The video's play state is toggled if this method is not implemented. + */ - (void)videoNodeWasTapped:(ASVideoNode *)videoNode; @end #endif + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASVideoNode.h.orig b/AsyncDisplayKit/ASVideoNode.h.orig new file mode 100644 index 0000000000..93266021a1 --- /dev/null +++ b/AsyncDisplayKit/ASVideoNode.h.orig @@ -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. + */ + +<<<<<<< HEAD +#import +#if TARGET_OS_IOS +======= +#import + +@class AVAsset, AVPlayer, AVPlayerItem; +>>>>>>> master +@protocol ASVideoNodeDelegate; + +NS_ASSUME_NONNULL_BEGIN + +// IMPORTANT NOTES: +// 1. Applications using ASVideoNode must link AVFoundation! (this provides the AV* classes below) +// 2. This is a relatively new component of AsyncDisplayKit. It has many useful features, but +// there is room for further expansion and optimization. Please report any issues or requests +// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues + +@interface ASVideoNode : ASControlNode + +- (void)play; +- (void)pause; +- (BOOL)isPlaying; + +@property (nullable, atomic, strong, readwrite) AVAsset *asset; + +@property (nullable, atomic, strong, readonly) AVPlayer *player; +@property (nullable, atomic, strong, readonly) AVPlayerItem *currentItem; + +/** + * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. + * If it leaves the visible interfaceState it will pause but will resume once it has returned. + */ +@property (nonatomic, assign, readwrite) BOOL shouldAutoplay; +@property (nonatomic, assign, readwrite) BOOL shouldAutorepeat; + +@property (nonatomic, assign, readwrite) BOOL muted; + +//! Defaults to AVLayerVideoGravityResizeAspect +@property (atomic) NSString *gravity; + +//! Defaults to an ASDefaultPlayButton instance. +@property (nullable, atomic) ASButtonNode *playButton; + +@property (nullable, atomic, weak, readwrite) id delegate; + +@end + +@protocol ASVideoNodeDelegate +@optional +/** + * @abstract Delegate method invoked when the node's video has played to its end time. + * @param videoNode The video node has played to its end time. + */ +- (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode; +/** + * @abstract Delegate method invoked the node is tapped. + * @param videoNode The video node that was tapped. + * @discussion The video's play state is toggled if this method is not implemented. + */ +- (void)videoNodeWasTapped:(ASVideoNode *)videoNode; +@end +<<<<<<< HEAD +#endif +======= + +NS_ASSUME_NONNULL_END +>>>>>>> master diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 458c62e82c..c7871caaa4 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -8,59 +8,220 @@ #if TARGET_OS_IOS #import "ASVideoNode.h" #import "ASDefaultPlayButton.h" + +static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { + return ASObjectIsEqual(asset1, asset2) + || ([asset1 isKindOfClass:[AVURLAsset class]] + && [asset2 isKindOfClass:[AVURLAsset class]] + && ASObjectIsEqual(((AVURLAsset *)asset1).URL, ((AVURLAsset *)asset2).URL)); +} + +static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { + if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspect]) { + return UIViewContentModeScaleAspectFit; + } else if ([videoGravity isEqual:AVLayerVideoGravityResizeAspectFill]) { + return UIViewContentModeScaleAspectFill; + } else { + return UIViewContentModeScaleToFill; + } +} + @interface ASVideoNode () { ASDN::RecursiveMutex _videoLock; __weak id _delegate; - + BOOL _shouldBePlaying; BOOL _shouldAutorepeat; BOOL _shouldAutoplay; BOOL _muted; - + AVAsset *_asset; - AVPlayerItem *_currentItem; + AVPlayerItem *_currentPlayerItem; AVPlayer *_player; - ASImageNode *_placeholderImageNode; + ASImageNode *_placeholderImageNode; // TODO: Make ASVideoNode an ASImageNode subclass; remove this. ASButtonNode *_playButton; ASDisplayNode *_playerNode; ASDisplayNode *_spinner; NSString *_gravity; - - dispatch_queue_t _previewQueue; } @end @implementation ASVideoNode +// TODO: Support preview images with HTTP Live Streaming videos. + +#pragma mark - Construction and Layout + - (instancetype)init { if (!(self = [super init])) { return nil; } - - _previewQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); self.playButton = [[ASDefaultPlayButton alloc] init]; - self.gravity = AVLayerVideoGravityResizeAspect; - [self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; - + return self; } +- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock +{ + ASDisplayNodeAssertNotSupported(); + return nil; +} + +- (ASDisplayNode *)constructPlayerNode +{ + ASDisplayNode * playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ + ASDN::MutexLocker l(_videoLock); + + AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; + if (!_player) { + [self constructCurrentPlayerItemFromInitData]; + _player = [AVPlayer playerWithPlayerItem:_currentPlayerItem]; + _player.muted = _muted; + } + playerLayer.player = _player; + playerLayer.videoGravity = [self gravity]; + return playerLayer; + }]; + + return playerNode; +} + +- (void)constructCurrentPlayerItemFromInitData +{ + ASDN::MutexLocker l(_videoLock); + + ASDisplayNodeAssert(_asset, @"ASVideoNode must be initialized with an AVAsset"); + [self removePlayerItemObservers]; + + if (_asset) { + if ([_asset.tracks count]) { + _currentPlayerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; + } else { + _currentPlayerItem = [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL]; + } + } + + if (_currentPlayerItem) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_currentPlayerItem]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:_currentPlayerItem]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:_currentPlayerItem]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + } +} + +- (void)removePlayerItemObservers +{ + ASDN::MutexLocker l(_videoLock); + + if (_currentPlayerItem) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + } +} + +- (void)didLoad +{ + [super didLoad]; + + ASDN::MutexLocker l(_videoLock); + + if (_shouldBePlaying) { + _playerNode = [self constructPlayerNode]; + [self insertSubnode:_playerNode atIndex:0]; + } else if (_asset) { + [self setPlaceholderImagefromAsset:_asset]; + } +} + +- (void)layout +{ + [super layout]; + + CGRect bounds = self.bounds; + + ASDN::MutexLocker l(_videoLock); + + _placeholderImageNode.frame = bounds; + _playerNode.frame = bounds; + _playButton.frame = bounds; + + CGFloat horizontalDiff = (bounds.size.width - _playButton.bounds.size.width)/2; + CGFloat verticalDiff = (bounds.size.height - _playButton.bounds.size.height)/2; + _playButton.hitTestSlop = UIEdgeInsetsMake(-verticalDiff, -horizontalDiff, -verticalDiff, -horizontalDiff); + + _spinner.bounds = CGRectMake(0, 0, 44, 44); + _spinner.position = CGPointMake(bounds.size.width/2, bounds.size.height/2); +} + +- (void)setPlaceholderImagefromAsset:(AVAsset*)asset +{ + ASPerformBlockOnBackgroundThread(^{ + ASDN::MutexLocker l(_videoLock); + + AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset]; + imageGenerator.appliesPreferredTrackTransform = YES; + NSArray *times = @[[NSValue valueWithCMTime:CMTimeMake(0, 1)]]; + + [imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) { + + ASDN::MutexLocker l(_videoLock); + + // Unfortunately it's not possible to generate a preview image for an HTTP live stream asset, so we'll give up here + // http://stackoverflow.com/questions/32112205/m3u8-file-avassetimagegenerator-error + if (image && _placeholderImageNode.image == nil) { + [self setPlaceholderImage:[UIImage imageWithCGImage:image]]; + } + }]; + }); +} + +- (void)setPlaceholderImage:(UIImage *)image +{ + ASDN::MutexLocker l(_videoLock); + + if (_placeholderImageNode == nil) { + _placeholderImageNode = [[ASImageNode alloc] init]; + _placeholderImageNode.layerBacked = YES; + _placeholderImageNode.contentMode = ASContentModeFromVideoGravity(_gravity); + } + + _placeholderImageNode.image = image; + + dispatch_async(dispatch_get_main_queue(), ^{ + ASDN::MutexLocker l(_videoLock); + + [self insertSubnode:_placeholderImageNode atIndex:0]; + [self setNeedsLayout]; + }); +} + - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState { - if (!(newState & ASInterfaceStateVisible)) { - if (oldState & ASInterfaceStateVisible) { + [super interfaceStateDidChange:newState fromState:oldState]; + + BOOL nowVisible = ASInterfaceStateIncludesVisible(newState); + BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState); + + ASDN::MutexLocker l(_videoLock); + + if (!nowVisible) { + if (wasVisible) { if (_shouldBePlaying) { [self pause]; _shouldBePlaying = YES; @@ -77,99 +238,30 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if ([[change objectForKey:@"new"] integerValue] == AVPlayerItemStatusReadyToPlay) { - if ([self.subnodes containsObject:_spinner]) { - [_spinner removeFromSupernode]; - _spinner = nil; - } - } + ASDN::MutexLocker l(_videoLock); - if ([[change objectForKey:@"new"] integerValue] == AVPlayerItemStatusFailed) { - - } -} - -- (void)didPlayToEnd:(NSNotification *)notification -{ - if (ASObjectIsEqual([[notification object] asset], _asset)) { - if ([_delegate respondsToSelector:@selector(videoPlaybackDidFinish:)]) { - [_delegate videoPlaybackDidFinish:self]; - } - [_player seekToTime:CMTimeMakeWithSeconds(0, 1)]; - - if (_shouldAutorepeat) { - [self play]; - } else { - [self pause]; - } - } -} - -- (void)layout -{ - [super layout]; - - CGRect bounds = self.bounds; - - _placeholderImageNode.frame = bounds; - _playerNode.frame = bounds; - _playerNode.layer.frame = bounds; - - _playButton.frame = bounds; - - CGFloat horizontalDiff = (bounds.size.width - _playButton.bounds.size.width)/2; - CGFloat verticalDiff = (bounds.size.height - _playButton.bounds.size.height)/2; - _playButton.hitTestSlop = UIEdgeInsetsMake(-verticalDiff, -horizontalDiff, -verticalDiff, -horizontalDiff); - - _spinner.bounds = CGRectMake(0, 0, 44, 44); - _spinner.position = CGPointMake(bounds.size.width/2, bounds.size.height/2); -} - -- (void)didLoad -{ - [super didLoad]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; - - if (_shouldBePlaying) { - _playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ - AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; - if (!_player) { - _player = [AVPlayer playerWithPlayerItem:[[AVPlayerItem alloc] initWithAsset:_asset]]; - _player.muted = _muted; + if (object == _currentPlayerItem && [keyPath isEqualToString:@"status"]) { + if (_currentPlayerItem.status == AVPlayerItemStatusReadyToPlay) { + if ([self.subnodes containsObject:_spinner]) { + [_spinner removeFromSupernode]; + _spinner = nil; } - playerLayer.player = _player; - playerLayer.videoGravity = [self gravity]; - return playerLayer; - }]; - - [self insertSubnode:_playerNode atIndex:0]; - } else { - dispatch_async(_previewQueue, ^{ - AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset]; - imageGenerator.appliesPreferredTrackTransform = YES; - [imageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:CMTimeMake(0, 1)]] completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) { - UIImage *theImage = [UIImage imageWithCGImage:image]; - - _placeholderImageNode = [[ASImageNode alloc] init]; - _placeholderImageNode.layerBacked = YES; - _placeholderImageNode.image = theImage; - - if ([_gravity isEqualToString:AVLayerVideoGravityResize]) { - _placeholderImageNode.contentMode = UIViewContentModeRedraw; + + // If we don't yet have a placeholder image update it now that we should have data available for it + if (_placeholderImageNode.image == nil) { + if (_currentPlayerItem && + _currentPlayerItem.tracks.count > 0 && + _currentPlayerItem.tracks[0].assetTrack && + _currentPlayerItem.tracks[0].assetTrack.asset) { + _asset = _currentPlayerItem.tracks[0].assetTrack.asset; + [self setPlaceholderImagefromAsset:_asset]; + [self setNeedsLayout]; } - if ([_gravity isEqualToString:AVLayerVideoGravityResizeAspect]) { - _placeholderImageNode.contentMode = UIViewContentModeScaleAspectFit; - } - if ([_gravity isEqual:AVLayerVideoGravityResizeAspectFill]) { - _placeholderImageNode.contentMode = UIViewContentModeScaleAspectFill; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - _placeholderImageNode.frame = self.bounds; - [self insertSubnode:_placeholderImageNode atIndex:0]; - }); - }]; - }); + } + + } else if (_currentPlayerItem.status == AVPlayerItemStatusFailed) { + + } } } @@ -186,32 +278,26 @@ } } -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - - (void)fetchData { [super fetchData]; - + @try { - [_currentItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; + [_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; } @catch (NSException * __unused exception) { NSLog(@"unnecessary removal in fetch data"); } - + { ASDN::MutexLocker l(_videoLock); - _currentItem = [[AVPlayerItem alloc] initWithAsset:_asset]; - [_currentItem addObserver:self forKeyPath:NSStringFromSelector(@selector(status)) options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; - + [self constructCurrentPlayerItemFromInitData]; + [_currentPlayerItem addObserver:self forKeyPath:NSStringFromSelector(@selector(status)) options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; + if (_player) { - [_player replaceCurrentItemWithPlayerItem:_currentItem]; + [_player replaceCurrentItemWithPlayerItem:_currentPlayerItem]; } else { - _player = [[AVPlayer alloc] initWithPlayerItem:_currentItem]; + _player = [[AVPlayer alloc] initWithPlayerItem:_currentPlayerItem]; _player.muted = _muted; } } @@ -230,6 +316,8 @@ - (void)visibilityDidChange:(BOOL)isVisible { + [super visibilityDidChange:isVisible]; + ASDN::MutexLocker l(_videoLock); if (_shouldAutoplay && _playerNode.isNodeLoaded) { @@ -240,24 +328,29 @@ if (isVisible) { if (_playerNode.isNodeLoaded) { if (!_player) { - _player = [AVPlayer playerWithPlayerItem:[[AVPlayerItem alloc] initWithAsset:_asset]]; + [self constructCurrentPlayerItemFromInitData]; + _player = [AVPlayer playerWithPlayerItem:_currentPlayerItem]; _player.muted = _muted; } ((AVPlayerLayer *)_playerNode.layer).player = _player; } - + if (_shouldBePlaying) { [self play]; } } } + #pragma mark - Video Properties - (void)setPlayButton:(ASButtonNode *)playButton { ASDN::MutexLocker l(_videoLock); - + + [_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; + [_playButton removeFromSupernode]; + _playButton = playButton; [self addSubnode:playButton]; @@ -276,10 +369,10 @@ { ASDN::MutexLocker l(_videoLock); - if (ASObjectIsEqual(((AVURLAsset *)asset).URL, ((AVURLAsset *)_asset).URL)) { + if (ASAssetIsEqual(asset, _asset)) { return; } - + _asset = asset; // FIXME: Adopt -setNeedsFetchData when it is available @@ -306,6 +399,7 @@ if (_playerNode.isNodeLoaded) { ((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity; } + _placeholderImageNode.contentMode = ASContentModeFromVideoGravity(gravity); _gravity = gravity; } @@ -319,14 +413,15 @@ - (BOOL)muted { ASDN::MutexLocker l(_videoLock); - + return _muted; } - (void)setMuted:(BOOL)muted { ASDN::MutexLocker l(_videoLock); - + + _player.muted = muted; _muted = muted; } @@ -346,16 +441,7 @@ } if (!_playerNode) { - _playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ - AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; - if (!_player) { - _player = [AVPlayer playerWithPlayerItem:[[AVPlayerItem alloc] initWithAsset:_asset]]; - _player.muted = _muted; - } - playerLayer.player = _player; - playerLayer.videoGravity = [self gravity]; - return playerLayer; - }]; + _playerNode = [self constructPlayerNode]; if ([self.subnodes containsObject:_playButton]) { [self insertSubnode:_playerNode belowSubnode:_playButton]; @@ -371,7 +457,7 @@ _playButton.alpha = 0.0; }]; - if (![self ready] && _shouldBePlaying && (self.interfaceState & ASInterfaceStateVisible)) { + if (![self ready] && _shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { [self addSubnode:_spinner]; [(UIActivityIndicatorView *)_spinner.view startAnimating]; } @@ -379,7 +465,7 @@ - (BOOL)ready { - return _currentItem.status == AVPlayerItemStatusReadyToPlay; + return _currentPlayerItem.status == AVPlayerItemStatusReadyToPlay; } - (void)pause @@ -401,6 +487,59 @@ return (_player.rate > 0 && !_player.error); } + +#pragma mark - Playback observers + +- (void)didPlayToEnd:(NSNotification *)notification +{ + if ([_delegate respondsToSelector:@selector(videoPlaybackDidFinish:)]) { + [_delegate videoPlaybackDidFinish:self]; + } + [_player seekToTime:kCMTimeZero]; + + if (_shouldAutorepeat) { + [self play]; + } else { + [self pause]; + } +} + +- (void)errorWhilePlaying:(NSNotification *)notification +{ + if ([notification.name isEqualToString:AVPlayerItemFailedToPlayToEndTimeNotification]) { + NSLog(@"Failed to play video"); + } else if ([notification.name isEqualToString:AVPlayerItemNewErrorLogEntryNotification]) { + AVPlayerItem *item = (AVPlayerItem *)notification.object; + AVPlayerItemErrorLogEvent *logEvent = item.errorLog.events.lastObject; + NSLog(@"AVPlayerItem error log entry added for video with error %@ status %@", item.error, + (item.status == AVPlayerItemStatusFailed ? @"FAILED" : [NSString stringWithFormat:@"%ld", (long)item.status])); + NSLog(@"Item is %@", item); + + if (logEvent) { + NSLog(@"Log code %ld domain %@ comment %@", (long)logEvent.errorStatusCode, logEvent.errorDomain, logEvent.errorComment); + } + } +} + +- (void)willEnterForeground:(NSNotification *)notification +{ + ASDN::MutexLocker l(_videoLock); + + if (_shouldBePlaying) { + [self play]; + } +} + +- (void)didEnterBackground:(NSNotification *)notification +{ + ASDN::MutexLocker l(_videoLock); + + if (_shouldBePlaying) { + [self pause]; + _shouldBePlaying = YES; + } +} + #pragma mark - Property Accessors for Tests - (ASDisplayNode *)spinner @@ -409,16 +548,22 @@ return _spinner; } -- (AVPlayerItem *)curentItem +- (ASImageNode *)placeholderImageNode { ASDN::MutexLocker l(_videoLock); - return _currentItem; + return _placeholderImageNode; +} + +- (AVPlayerItem *)currentItem +{ + ASDN::MutexLocker l(_videoLock); + return _currentPlayerItem; } - (void)setCurrentItem:(AVPlayerItem *)currentItem { ASDN::MutexLocker l(_videoLock); - _currentItem = currentItem; + _currentPlayerItem = currentItem; } - (ASDisplayNode *)playerNode @@ -427,6 +572,18 @@ return _playerNode; } +- (void)setPlayerNode:(ASDisplayNode *)playerNode +{ + ASDN::MutexLocker l(_videoLock); + _playerNode = playerNode; +} + +- (void)setPlayer:(AVPlayer *)player +{ + ASDN::MutexLocker l(_videoLock); + _player = player; +} + - (BOOL)shouldBePlaying { ASDN::MutexLocker l(_videoLock); @@ -437,9 +594,11 @@ - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; + [_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; + [self removePlayerItemObservers]; + @try { - [_currentItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; + [_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; } @catch (NSException * __unused exception) { NSLog(@"unnecessary removal in dealloc"); diff --git a/AsyncDisplayKit/ASVideoNode.mm.orig b/AsyncDisplayKit/ASVideoNode.mm.orig new file mode 100644 index 0000000000..91de668282 --- /dev/null +++ b/AsyncDisplayKit/ASVideoNode.mm.orig @@ -0,0 +1,612 @@ +/* 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. + */ +#if TARGET_OS_IOS +#import "ASVideoNode.h" +#import "ASDefaultPlayButton.h" +<<<<<<< HEAD +======= + +static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { + return ASObjectIsEqual(asset1, asset2) + || ([asset1 isKindOfClass:[AVURLAsset class]] + && [asset2 isKindOfClass:[AVURLAsset class]] + && ASObjectIsEqual(((AVURLAsset *)asset1).URL, ((AVURLAsset *)asset2).URL)); +} + +static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { + if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspect]) { + return UIViewContentModeScaleAspectFit; + } else if ([videoGravity isEqual:AVLayerVideoGravityResizeAspectFill]) { + return UIViewContentModeScaleAspectFill; + } else { + return UIViewContentModeScaleToFill; + } +} + +>>>>>>> master +@interface ASVideoNode () +{ + ASDN::RecursiveMutex _videoLock; + + __weak id _delegate; + + BOOL _shouldBePlaying; + + BOOL _shouldAutorepeat; + BOOL _shouldAutoplay; + + BOOL _muted; + + AVAsset *_asset; + + AVPlayerItem *_currentPlayerItem; + AVPlayer *_player; + + ASImageNode *_placeholderImageNode; // TODO: Make ASVideoNode an ASImageNode subclass; remove this. + + ASButtonNode *_playButton; + ASDisplayNode *_playerNode; + ASDisplayNode *_spinner; + NSString *_gravity; +} + +@end + +@implementation ASVideoNode + +// TODO: Support preview images with HTTP Live Streaming videos. + +#pragma mark - Construction and Layout + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + self.playButton = [[ASDefaultPlayButton alloc] init]; + self.gravity = AVLayerVideoGravityResizeAspect; + [self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; + + return self; +} + +- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock +{ + ASDisplayNodeAssertNotSupported(); + return nil; +} + +- (ASDisplayNode *)constructPlayerNode +{ + ASDisplayNode * playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ + ASDN::MutexLocker l(_videoLock); + + AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; + if (!_player) { + [self constructCurrentPlayerItemFromInitData]; + _player = [AVPlayer playerWithPlayerItem:_currentPlayerItem]; + _player.muted = _muted; + } + playerLayer.player = _player; + playerLayer.videoGravity = [self gravity]; + return playerLayer; + }]; + + return playerNode; +} + +- (void)constructCurrentPlayerItemFromInitData +{ + ASDN::MutexLocker l(_videoLock); + + ASDisplayNodeAssert(_asset, @"ASVideoNode must be initialized with an AVAsset"); + [self removePlayerItemObservers]; + + if (_asset) { + if ([_asset.tracks count]) { + _currentPlayerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; + } else { + _currentPlayerItem = [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL]; + } + } + + if (_currentPlayerItem) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_currentPlayerItem]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:_currentPlayerItem]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:_currentPlayerItem]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + } +} + +- (void)removePlayerItemObservers +{ + ASDN::MutexLocker l(_videoLock); + + if (_currentPlayerItem) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + } +} + +- (void)didLoad +{ + [super didLoad]; + + ASDN::MutexLocker l(_videoLock); + + if (_shouldBePlaying) { + _playerNode = [self constructPlayerNode]; + [self insertSubnode:_playerNode atIndex:0]; + } else if (_asset) { + [self setPlaceholderImagefromAsset:_asset]; + } +} + +- (void)layout +{ + [super layout]; + + CGRect bounds = self.bounds; + + ASDN::MutexLocker l(_videoLock); + + _placeholderImageNode.frame = bounds; + _playerNode.frame = bounds; + _playButton.frame = bounds; + + CGFloat horizontalDiff = (bounds.size.width - _playButton.bounds.size.width)/2; + CGFloat verticalDiff = (bounds.size.height - _playButton.bounds.size.height)/2; + _playButton.hitTestSlop = UIEdgeInsetsMake(-verticalDiff, -horizontalDiff, -verticalDiff, -horizontalDiff); + + _spinner.bounds = CGRectMake(0, 0, 44, 44); + _spinner.position = CGPointMake(bounds.size.width/2, bounds.size.height/2); +} + +- (void)setPlaceholderImagefromAsset:(AVAsset*)asset +{ + ASPerformBlockOnBackgroundThread(^{ + ASDN::MutexLocker l(_videoLock); + + AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset]; + imageGenerator.appliesPreferredTrackTransform = YES; + NSArray *times = @[[NSValue valueWithCMTime:CMTimeMake(0, 1)]]; + + [imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) { + + ASDN::MutexLocker l(_videoLock); + + // Unfortunately it's not possible to generate a preview image for an HTTP live stream asset, so we'll give up here + // http://stackoverflow.com/questions/32112205/m3u8-file-avassetimagegenerator-error + if (image && _placeholderImageNode.image == nil) { + [self setPlaceholderImage:[UIImage imageWithCGImage:image]]; + } + }]; + }); +} + +- (void)setPlaceholderImage:(UIImage *)image +{ + ASDN::MutexLocker l(_videoLock); + + if (_placeholderImageNode == nil) { + _placeholderImageNode = [[ASImageNode alloc] init]; + _placeholderImageNode.layerBacked = YES; + _placeholderImageNode.contentMode = ASContentModeFromVideoGravity(_gravity); + } + + _placeholderImageNode.image = image; + + dispatch_async(dispatch_get_main_queue(), ^{ + ASDN::MutexLocker l(_videoLock); + + [self insertSubnode:_placeholderImageNode atIndex:0]; + [self setNeedsLayout]; + }); +} + +- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState +{ + [super interfaceStateDidChange:newState fromState:oldState]; + + BOOL nowVisible = ASInterfaceStateIncludesVisible(newState); + BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState); + + ASDN::MutexLocker l(_videoLock); + + if (!nowVisible) { + if (wasVisible) { + if (_shouldBePlaying) { + [self pause]; + _shouldBePlaying = YES; + } + [(UIActivityIndicatorView *)_spinner.view stopAnimating]; + [_spinner removeFromSupernode]; + } + } else { + if (_shouldBePlaying) { + [self play]; + } + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + ASDN::MutexLocker l(_videoLock); + + if (object == _currentPlayerItem && [keyPath isEqualToString:@"status"]) { + if (_currentPlayerItem.status == AVPlayerItemStatusReadyToPlay) { + if ([self.subnodes containsObject:_spinner]) { + [_spinner removeFromSupernode]; + _spinner = nil; + } + + // If we don't yet have a placeholder image update it now that we should have data available for it + if (_placeholderImageNode.image == nil) { + if (_currentPlayerItem && + _currentPlayerItem.tracks.count > 0 && + _currentPlayerItem.tracks[0].assetTrack && + _currentPlayerItem.tracks[0].assetTrack.asset) { + _asset = _currentPlayerItem.tracks[0].assetTrack.asset; + [self setPlaceholderImagefromAsset:_asset]; + [self setNeedsLayout]; + } + } + + } else if (_currentPlayerItem.status == AVPlayerItemStatusFailed) { + + } + } +} + +- (void)tapped +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(videoNodeWasTapped:)]) { + [self.delegate videoNodeWasTapped:self]; + } else { + if (_shouldBePlaying) { + [self pause]; + } else { + [self play]; + } + } +} + +- (void)fetchData +{ + [super fetchData]; + + @try { + [_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; + } + @catch (NSException * __unused exception) { + NSLog(@"unnecessary removal in fetch data"); + } + + { + ASDN::MutexLocker l(_videoLock); + [self constructCurrentPlayerItemFromInitData]; + [_currentPlayerItem addObserver:self forKeyPath:NSStringFromSelector(@selector(status)) options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; + + if (_player) { + [_player replaceCurrentItemWithPlayerItem:_currentPlayerItem]; + } else { + _player = [[AVPlayer alloc] initWithPlayerItem:_currentPlayerItem]; + _player.muted = _muted; + } + } +} + +- (void)clearFetchedData +{ + [super clearFetchedData]; + + { + ASDN::MutexLocker l(_videoLock); + ((AVPlayerLayer *)_playerNode.layer).player = nil; + _player = nil; + } +} + +- (void)visibilityDidChange:(BOOL)isVisible +{ + [super visibilityDidChange:isVisible]; + + ASDN::MutexLocker l(_videoLock); + + if (_shouldAutoplay && _playerNode.isNodeLoaded) { + [self play]; + } else if (_shouldAutoplay) { + _shouldBePlaying = YES; + } + if (isVisible) { + if (_playerNode.isNodeLoaded) { + if (!_player) { + [self constructCurrentPlayerItemFromInitData]; + _player = [AVPlayer playerWithPlayerItem:_currentPlayerItem]; + _player.muted = _muted; + } + ((AVPlayerLayer *)_playerNode.layer).player = _player; + } + + if (_shouldBePlaying) { + [self play]; + } + } +} + + +#pragma mark - Video Properties + +- (void)setPlayButton:(ASButtonNode *)playButton +{ + ASDN::MutexLocker l(_videoLock); + + [_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; + [_playButton removeFromSupernode]; + + _playButton = playButton; + + [self addSubnode:playButton]; + + [_playButton addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; +} + +- (ASButtonNode *)playButton +{ + ASDN::MutexLocker l(_videoLock); + + return _playButton; +} + +- (void)setAsset:(AVAsset *)asset +{ + ASDN::MutexLocker l(_videoLock); + + if (ASAssetIsEqual(asset, _asset)) { + return; + } + + _asset = asset; + + // FIXME: Adopt -setNeedsFetchData when it is available + if (self.interfaceState & ASInterfaceStateFetchData) { + [self fetchData]; + } +} + +- (AVAsset *)asset +{ + ASDN::MutexLocker l(_videoLock); + return _asset; +} + +- (AVPlayer *)player +{ + ASDN::MutexLocker l(_videoLock); + return _player; +} + +- (void)setGravity:(NSString *)gravity +{ + ASDN::MutexLocker l(_videoLock); + if (_playerNode.isNodeLoaded) { + ((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity; + } + _placeholderImageNode.contentMode = ASContentModeFromVideoGravity(gravity); + _gravity = gravity; +} + +- (NSString *)gravity +{ + ASDN::MutexLocker l(_videoLock); + + return _gravity; +} + +- (BOOL)muted +{ + ASDN::MutexLocker l(_videoLock); + + return _muted; +} + +- (void)setMuted:(BOOL)muted +{ + ASDN::MutexLocker l(_videoLock); + + _player.muted = muted; + _muted = muted; +} + +#pragma mark - Video Playback + +- (void)play +{ + ASDN::MutexLocker l(_videoLock); + + if (!_spinner) { + _spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ + UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; + spinnnerView.color = [UIColor whiteColor]; + + return spinnnerView; + }]; + } + + if (!_playerNode) { + _playerNode = [self constructPlayerNode]; + + if ([self.subnodes containsObject:_playButton]) { + [self insertSubnode:_playerNode belowSubnode:_playButton]; + } else { + [self addSubnode:_playerNode]; + } + } + + [_player play]; + _shouldBePlaying = YES; + + [UIView animateWithDuration:0.15 animations:^{ + _playButton.alpha = 0.0; + }]; + + if (![self ready] && _shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { + [self addSubnode:_spinner]; + [(UIActivityIndicatorView *)_spinner.view startAnimating]; + } +} + +- (BOOL)ready +{ + return _currentPlayerItem.status == AVPlayerItemStatusReadyToPlay; +} + +- (void)pause +{ + ASDN::MutexLocker l(_videoLock); + + [_player pause]; + [((UIActivityIndicatorView *)_spinner.view) stopAnimating]; + _shouldBePlaying = NO; + [UIView animateWithDuration:0.15 animations:^{ + _playButton.alpha = 1.0; + }]; +} + +- (BOOL)isPlaying +{ + ASDN::MutexLocker l(_videoLock); + + return (_player.rate > 0 && !_player.error); +} + + +#pragma mark - Playback observers + +- (void)didPlayToEnd:(NSNotification *)notification +{ + if ([_delegate respondsToSelector:@selector(videoPlaybackDidFinish:)]) { + [_delegate videoPlaybackDidFinish:self]; + } + [_player seekToTime:kCMTimeZero]; + + if (_shouldAutorepeat) { + [self play]; + } else { + [self pause]; + } +} + +- (void)errorWhilePlaying:(NSNotification *)notification +{ + if ([notification.name isEqualToString:AVPlayerItemFailedToPlayToEndTimeNotification]) { + NSLog(@"Failed to play video"); + } else if ([notification.name isEqualToString:AVPlayerItemNewErrorLogEntryNotification]) { + AVPlayerItem *item = (AVPlayerItem *)notification.object; + AVPlayerItemErrorLogEvent *logEvent = item.errorLog.events.lastObject; + NSLog(@"AVPlayerItem error log entry added for video with error %@ status %@", item.error, + (item.status == AVPlayerItemStatusFailed ? @"FAILED" : [NSString stringWithFormat:@"%ld", (long)item.status])); + NSLog(@"Item is %@", item); + + if (logEvent) { + NSLog(@"Log code %ld domain %@ comment %@", (long)logEvent.errorStatusCode, logEvent.errorDomain, logEvent.errorComment); + } + } +} + +- (void)willEnterForeground:(NSNotification *)notification +{ + ASDN::MutexLocker l(_videoLock); + + if (_shouldBePlaying) { + [self play]; + } +} + +- (void)didEnterBackground:(NSNotification *)notification +{ + ASDN::MutexLocker l(_videoLock); + + if (_shouldBePlaying) { + [self pause]; + _shouldBePlaying = YES; + } +} + +#pragma mark - Property Accessors for Tests + +- (ASDisplayNode *)spinner +{ + ASDN::MutexLocker l(_videoLock); + return _spinner; +} + +- (ASImageNode *)placeholderImageNode +{ + ASDN::MutexLocker l(_videoLock); + return _placeholderImageNode; +} + +- (AVPlayerItem *)currentItem +{ + ASDN::MutexLocker l(_videoLock); + return _currentPlayerItem; +} + +- (void)setCurrentItem:(AVPlayerItem *)currentItem +{ + ASDN::MutexLocker l(_videoLock); + _currentPlayerItem = currentItem; +} + +- (ASDisplayNode *)playerNode +{ + ASDN::MutexLocker l(_videoLock); + return _playerNode; +} + +- (void)setPlayerNode:(ASDisplayNode *)playerNode +{ + ASDN::MutexLocker l(_videoLock); + _playerNode = playerNode; +} + +- (void)setPlayer:(AVPlayer *)player +{ + ASDN::MutexLocker l(_videoLock); + _player = player; +} + +- (BOOL)shouldBePlaying +{ + ASDN::MutexLocker l(_videoLock); + return _shouldBePlaying; +} + +#pragma mark - Lifecycle + +- (void)dealloc +{ + [_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; + [self removePlayerItemObservers]; + + @try { + [_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; + } + @catch (NSException * __unused exception) { + NSLog(@"unnecessary removal in dealloc"); + } +} + +@end +#endif diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 6f052a5c60..db5b1e5b4d 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -11,10 +11,12 @@ #import "ASDimension.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Beta.h" +#import "ASRangeControllerUpdateRangeProtocol+Beta.h" @implementation ASViewController { BOOL _ensureDisplayed; + BOOL _automaticallyAdjustRangeModeBasedOnViewEvents; } - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil @@ -38,6 +40,8 @@ ASDisplayNodeAssertNotNil(node, @"Node must not be nil"); ASDisplayNodeAssertTrue(!node.layerBacked); _node = node; + + _automaticallyAdjustRangeModeBasedOnViewEvents = NO; return self; } @@ -45,7 +49,21 @@ - (void)loadView { ASDisplayNodeAssertTrue(!_node.layerBacked); - self.view = _node.view; + + // Apple applies a frame and autoresizing masks we need. Allocating a view is not + // nearly as expensive as adding and removing it from a hierarchy, and fortunately + // we can avoid that here. Enabling layerBacking on a single node in the hierarchy + // will have a greater performance benefit than the impact of this transient view. + [super loadView]; + UIView *view = self.view; + CGRect frame = view.frame; + UIViewAutoresizing autoresizingMask = view.autoresizingMask; + + // We have what we need, so now create and assign the view we actually want. + view = _node.view; + _node.frame = frame; + _node.autoresizingMask = autoresizingMask; + self.view = view; } - (void)viewWillLayoutSubviews @@ -67,10 +85,41 @@ { [super viewWillAppear:animated]; _ensureDisplayed = YES; + [_node measureWithSizeRange:[self nodeConstrainedSize]]; [_node recursivelyFetchData]; + + [self updateCurrentRangeModeWithModeIfPossible:ASLayoutRangeModeFull]; } -// MARK: - Layout Helpers +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + + [self updateCurrentRangeModeWithModeIfPossible:ASLayoutRangeModeMinimum]; +} + +#pragma mark - Automatic range mode + +- (BOOL)automaticallyAdjustRangeModeBasedOnViewEvents +{ + return _automaticallyAdjustRangeModeBasedOnViewEvents; +} + +- (void)setAutomaticallyAdjustRangeModeBasedOnViewEvents:(BOOL)automaticallyAdjustRangeModeBasedOnViewEvents +{ + _automaticallyAdjustRangeModeBasedOnViewEvents = automaticallyAdjustRangeModeBasedOnViewEvents; +} + +- (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode +{ + if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { return; } + if (![_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]) { return; } + + id updateRangeNode = (id)_node; + [updateRangeNode updateCurrentRangeWithMode:rangeMode]; +} + +#pragma mark - Layout Helpers - (ASSizeRange)nodeConstrainedSize { diff --git a/AsyncDisplayKit/AsyncDisplayKit+Debug.h b/AsyncDisplayKit/AsyncDisplayKit+Debug.h new file mode 100644 index 0000000000..b2667d1e65 --- /dev/null +++ b/AsyncDisplayKit/AsyncDisplayKit+Debug.h @@ -0,0 +1,38 @@ +// +// AsyncDisplayKit+Debug.h +// AsyncDisplayKit +// +// Created by Hannah Troisi on 3/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASControlNode.h" +#import "ASImageNode.h" + +@interface ASControlNode (Debugging) + +/** + Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only. + NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!! + Overlay = translucent GREEN color, + edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE, + edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond + overlay rect, but can't be visualized). + @param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class. + */ ++ (void)setEnableHitTestDebug:(BOOL)enable; + +@end + +@interface ASImageNode (Debugging) + +/** +* Enables an ASImageNode debug label that shows the ratio of pixels in the source image to those in +* the displayed bounds (including cropRect). This helps detect excessive image fetching / downscaling, +* as well as upscaling (such as providing a URL not suitable for a Retina device). For dev purposes only. +* @param enabled Specify YES to show the label on all ASImageNodes with non-1.0x source-to-bounds pixel ratio. +*/ ++ (void)setShouldShowImageScalingOverlay:(BOOL)show; ++ (BOOL)shouldShowImageScalingOverlay; + +@end diff --git a/AsyncDisplayKit/AsyncDisplayKit+Debug.m b/AsyncDisplayKit/AsyncDisplayKit+Debug.m new file mode 100644 index 0000000000..c0cd93a6ce --- /dev/null +++ b/AsyncDisplayKit/AsyncDisplayKit+Debug.m @@ -0,0 +1,26 @@ +// +// AsyncDisplayKit+Debug.m +// AsyncDisplayKit +// +// Created by Hannah Troisi on 3/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "AsyncDisplayKit+Debug.h" +#import "ASDisplayNode+Subclasses.h" + +static BOOL __shouldShowImageScalingOverlay = NO; + +@implementation ASImageNode (Debugging) + ++ (void)setShouldShowImageScalingOverlay:(BOOL)show; +{ + __shouldShowImageScalingOverlay = show; +} + ++ (BOOL)shouldShowImageScalingOverlay +{ + return __shouldShowImageScalingOverlay; +} + +@end diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index b703f44bc1..ad24302d62 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -11,10 +11,11 @@ #import #import +#import #import #import #import - +#import #import #import @@ -34,15 +35,18 @@ #import #import +#import #import #import #import +#import #import #import #import #import +#import #import #import #import @@ -61,11 +65,10 @@ #import #import #import -#import #import #import #import -#import +#import #import #import #import @@ -73,3 +76,9 @@ #import #import #import +#import +#import + +#import + +#import diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm index 38828861e7..b7426bd1c5 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -33,22 +33,43 @@ extern BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningPar _tuningParameters = std::vector> (ASLayoutRangeModeCount, std::vector (ASLayoutRangeTypeCount)); + _tuningParameters[ASLayoutRangeModeFull][ASLayoutRangeTypeDisplay] = { + .leadingBufferScreenfuls = 1.0, + .trailingBufferScreenfuls = 0.5 + }; + _tuningParameters[ASLayoutRangeModeFull][ASLayoutRangeTypeFetchData] = { + .leadingBufferScreenfuls = 2.5, + .trailingBufferScreenfuls = 1.5 + }; + _tuningParameters[ASLayoutRangeModeMinimum][ASLayoutRangeTypeDisplay] = { .leadingBufferScreenfuls = 0.25, .trailingBufferScreenfuls = 0.25 }; _tuningParameters[ASLayoutRangeModeMinimum][ASLayoutRangeTypeFetchData] = { - .leadingBufferScreenfuls = 1, - .trailingBufferScreenfuls = 1 + .leadingBufferScreenfuls = 0.25, + .trailingBufferScreenfuls = 0.5 }; - _tuningParameters[ASLayoutRangeModeFull][ASLayoutRangeTypeDisplay] = { - .leadingBufferScreenfuls = 1.5, - .trailingBufferScreenfuls = 0.75 + _tuningParameters[ASLayoutRangeModeVisibleOnly][ASLayoutRangeTypeDisplay] = { + .leadingBufferScreenfuls = 0, + .trailingBufferScreenfuls = 0 }; - _tuningParameters[ASLayoutRangeModeFull][ASLayoutRangeTypeFetchData] = { - .leadingBufferScreenfuls = 3, - .trailingBufferScreenfuls = 2 + _tuningParameters[ASLayoutRangeModeVisibleOnly][ASLayoutRangeTypeFetchData] = { + .leadingBufferScreenfuls = 0, + .trailingBufferScreenfuls = 0 + }; + + // The Low Memory range mode has special handling. Because a zero range still includes the visible area / bounds, + // in order to implement the behavior of releasing all graphics memory (backing stores), ASRangeController must check + // for this range mode and use an empty set for displayIndexPaths rather than querying the ASLayoutController for the indexPaths. + _tuningParameters[ASLayoutRangeModeLowMemory][ASLayoutRangeTypeDisplay] = { + .leadingBufferScreenfuls = 0, + .trailingBufferScreenfuls = 0 + }; + _tuningParameters[ASLayoutRangeModeLowMemory][ASLayoutRangeTypeFetchData] = { + .leadingBufferScreenfuls = 0, + .trailingBufferScreenfuls = 0 }; return self; diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm index 73301919cc..1b546f1eaf 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm @@ -240,7 +240,7 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // associate metadata with it NSMutableDictionary *callbackData = [NSMutableDictionary dictionary]; - callbackData[kASBasicImageDownloaderContextCallbackQueue] = callbackQueue ?: dispatch_get_main_queue(); + callbackData[kASBasicImageDownloaderContextCallbackQueue] = callbackQueue ? : dispatch_get_main_queue(); if (downloadProgressBlock) { callbackData[kASBasicImageDownloaderContextProgressBlock] = [downloadProgressBlock copy]; diff --git a/AsyncDisplayKit/Details/ASBatchContext.h b/AsyncDisplayKit/Details/ASBatchContext.h index 452582f9cf..dc1986a792 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.h +++ b/AsyncDisplayKit/Details/ASBatchContext.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @interface ASBatchContext : NSObject /** - * Retreive the state of the current batch process. + * Retrieve the state of the current batch process. * * @returns A boolean reflecting if the owner of the context object is fetching another batch. */ @@ -44,7 +44,7 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)batchFetchingWasCancelled; /** - * Notify the context object that something has interupted the batch fetching process. + * Notify the context object that something has interrupted the batch fetching process. * * @discussion Call this method only when something has corrupted the batch fetching process. Calling this method should * be left to the owner of the batch process unless there is a specific purpose. diff --git a/AsyncDisplayKit/Details/ASBatchContext.mm b/AsyncDisplayKit/Details/ASBatchContext.mm index 4833cbd826..4cb5b96f1b 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.mm +++ b/AsyncDisplayKit/Details/ASBatchContext.mm @@ -45,6 +45,12 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { return _state == ASBatchContextStateCancelled; } +- (void)beginBatchFetching +{ + ASDN::MutexLocker l(_propertyLock); + _state = ASBatchContextStateFetching; +} + - (void)completeBatchFetching:(BOOL)didComplete { if (didComplete) { @@ -53,12 +59,6 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { } } -- (void)beginBatchFetching -{ - ASDN::MutexLocker l(_propertyLock); - _state = ASBatchContextStateFetching; -} - - (void)cancelBatchFetching { ASDN::MutexLocker l(_propertyLock); diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.m b/AsyncDisplayKit/Details/ASChangeSetDataController.m index 666ea86ce7..1441965cb9 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.m +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.m @@ -15,7 +15,7 @@ @interface ASChangeSetDataController () -@property (nonatomic, assign) NSUInteger batchUpdateCounter; +@property (nonatomic, assign) NSUInteger changeSetBatchUpdateCounter; @property (nonatomic, strong) _ASHierarchyChangeSet *changeSet; @end @@ -28,7 +28,7 @@ return nil; } - _batchUpdateCounter = 0; + _changeSetBatchUpdateCounter = 0; return self; } @@ -38,18 +38,18 @@ - (void)beginUpdates { ASDisplayNodeAssertMainThread(); - if (_batchUpdateCounter == 0) { + if (_changeSetBatchUpdateCounter == 0) { _changeSet = [_ASHierarchyChangeSet new]; } - _batchUpdateCounter++; + _changeSetBatchUpdateCounter++; } - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { ASDisplayNodeAssertMainThread(); - _batchUpdateCounter--; + _changeSetBatchUpdateCounter--; - if (_batchUpdateCounter == 0) { + if (_changeSetBatchUpdateCounter == 0) { [_changeSet markCompleted]; [super beginUpdates]; @@ -86,7 +86,7 @@ - (BOOL)batchUpdating { - BOOL batchUpdating = (_batchUpdateCounter != 0); + BOOL batchUpdating = (_changeSetBatchUpdateCounter != 0); // _changeSet must be available during batch update ASDisplayNodeAssertTrue(batchUpdating == (_changeSet != nil)); return batchUpdating; diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 2408a57e1f..7026024ff8 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -13,6 +13,7 @@ #import "ASCellNode.h" #import "ASDisplayNodeInternal.h" #import "ASDataController+Subclasses.h" +#import "ASIndexedNodeContext.h" //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -26,16 +27,14 @@ @end @implementation ASCollectionDataController { - NSMutableDictionary *> *_pendingNodes; - NSMutableDictionary *> *_pendingIndexPaths; + NSMutableDictionary *> *_pendingContexts; } - (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled { self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled]; if (self != nil) { - _pendingNodes = [NSMutableDictionary dictionary]; - _pendingIndexPaths = [NSMutableDictionary dictionary]; + _pendingContexts = [NSMutableDictionary dictionary]; } return self; } @@ -44,20 +43,15 @@ { for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@", kind); - NSMutableArray *indexPaths = [NSMutableArray array]; - NSMutableArray *nodes = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; - _pendingNodes[kind] = nodes; - _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + NSMutableArray *contexts = [NSMutableArray array]; + [self _populateSupplementaryNodesOfKind:kind withMutableContexts:contexts]; + _pendingContexts[kind] = contexts; } } - (void)willReloadData { - [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { + [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *contexts, BOOL *stop) { // Remove everything that existed before the reload, now that we're ready to insert replacements NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; @@ -74,11 +68,10 @@ } [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; + [_pendingContexts removeObjectForKey:kind]; }]; } @@ -86,31 +79,25 @@ { for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections); - NSMutableArray *nodes = [NSMutableArray array]; - NSMutableArray *indexPaths = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; - _pendingNodes[kind] = nodes; - _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + NSMutableArray *contexts = [NSMutableArray array]; + [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; + _pendingContexts[kind] = contexts; } } - (void)willInsertSections:(NSIndexSet *)sections { - [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { + [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *contexts, BOOL *stop) { NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; + [_pendingContexts removeObjectForKey:kind]; }]; } @@ -127,28 +114,22 @@ - (void)prepareForReloadSections:(NSIndexSet *)sections { for (NSString *kind in [self supplementaryKinds]) { - NSMutableArray *nodes = [NSMutableArray array]; - NSMutableArray *indexPaths = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; - _pendingNodes[kind] = nodes; - _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + NSMutableArray *contexts = [NSMutableArray array]; + [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; + _pendingContexts[kind] = contexts; } } - (void)willReloadSections:(NSIndexSet *)sections { - [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { + [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *contexts, BOOL *stop) { NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; // reinsert the elements - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; + [_pendingContexts removeObjectForKey:kind]; }]; } @@ -169,7 +150,7 @@ } } -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths +- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableContexts:(NSMutableArray *)contexts { NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; for (NSUInteger i = 0; i < sectionCount; i++) { @@ -177,7 +158,7 @@ NSUInteger rowCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:i]; for (NSUInteger j = 0; j < rowCount; j++) { NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; - [indexPaths addObject:indexPath]; + ASCellNodeBlock supplementaryCellBlock; if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; @@ -185,19 +166,24 @@ ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; supplementaryCellBlock = ^{ return supplementaryNode; }; } - [nodes addObject:supplementaryCellBlock]; + + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock + indexPath:indexPath + constrainedSize:constrainedSize]; + [contexts addObject:context]; } } } -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths +- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray *)contexts { [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { NSUInteger rowNum = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:idx]; NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; for (NSUInteger i = 0; i < rowNum; i++) { NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; - [indexPaths addObject:indexPath]; + ASCellNodeBlock supplementaryCellBlock; if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; @@ -205,7 +191,12 @@ ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; supplementaryCellBlock = ^{ return supplementaryNode; }; } - [nodes addObject:supplementaryCellBlock]; + + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock + indexPath:indexPath + constrainedSize:constrainedSize]; + [contexts addObject:context]; } }]; } diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index b2ee2ae8a8..cd640c3e2a 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -6,8 +6,9 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#pragma once +#import #import @class ASCollectionView; diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m index 6168730878..3fe377f3a3 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m @@ -104,11 +104,7 @@ - (BOOL)layoutHasSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView { CGSize size = [self sizeForSupplementaryViewOfKind:kind inSection:section collectionView:collectionView]; - if ([self usedLayoutValueForSize:size] > 0) { - return YES; - } else { - return NO; - } + return [self usedLayoutValueForSize:size] > 0; } - (CGFloat)usedLayoutValueForSize:(CGSize)size diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm index 0d5f69bdf5..62523a40be 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm @@ -60,11 +60,13 @@ typedef struct ASRangeGeometry ASRangeGeometry; { NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds]; NSMutableSet *indexPathSet = [NSMutableSet setWithCapacity:layoutAttributes.count]; + for (UICollectionViewLayoutAttributes *la in layoutAttributes) { //ASDisplayNodeAssert(![indexPathSet containsObject:la.indexPath], @"Shouldn't already contain indexPath"); ASDisplayNodeAssert(la.representedElementCategory != UICollectionElementCategoryDecorationView, @"UICollectionView decoration views are not supported by ASCollectionView"); [indexPathSet addObject:la.indexPath]; } + return indexPathSet; } diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 32d787910e..51b7548f50 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -6,10 +6,11 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#ifndef ASDataControllerSubclasses_Included -#define ASDataControllerSubclasses_Included +#pragma once -//#import "ASDataController.h" +@class ASIndexedNodeContext; + +typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NSArray *indexPaths); @interface ASDataController (Subclasses) @@ -35,15 +36,15 @@ /** * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. */ -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)batchLayoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock; -/* +/** * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. * * @discussion Once nodes have loaded their views, we can't layout in the background so this is a chance * to do so immediately on the main thread. */ -- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; +- (void)layoutLoadedNodes:(NSArray *)nodes fromContexts:(NSArray *)contexts ofKind:(NSString *)kind; /** * Provides the size range for a specific node during the layout process. @@ -55,12 +56,12 @@ /** * Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes. */ -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock; /** * Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes. */ -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock; /** * Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished. @@ -160,5 +161,3 @@ - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection; @end - -#endif diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 6648275d9b..190119ed62 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -6,8 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#ifndef ASDataController_Included -#define ASDataController_Included +#pragma once #import #import @@ -131,20 +130,12 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; * @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 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`, + * will take the responsibility to avoid data inconsistency. 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 - * - * @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)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - /** @name Data Updating */ - (void)beginUpdates; @@ -181,6 +172,8 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; - (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)waitUntilAllUpdatesAreCommitted; + /** @name Data Querying */ - (NSUInteger)numberOfSections; @@ -201,5 +194,3 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; @end NS_ASSUME_NONNULL_END - -#endif diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 32d62adac2..9d8a2c81a7 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -19,6 +19,8 @@ #import "ASMainSerialQueue.h" #import "ASMultidimensionalArrayUtils.h" #import "ASThread.h" +#import "ASIndexedNodeContext.h" +#import "ASDataController+Subclasses.h" //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -32,7 +34,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; @interface ASDataController () { NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. - NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. + NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propagated to _completedNodes. ASMainSerialQueue *_mainSerialQueue; @@ -40,6 +42,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. BOOL _asyncDataFetchingEnabled; + + BOOL _initialReloadDataHasBeenCalled; BOOL _delegateDidInsertNodes; BOOL _delegateDidDeleteNodes; @@ -110,29 +114,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)batchLayoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock { NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; + NSUInteger count = contexts.count; // Processing in batches - for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { - NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); - NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; - NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; - [self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock]; + for (NSUInteger i = 0; i < count; i += blockSize) { + NSRange batchedRange = NSMakeRange(i, MIN(count - i, blockSize)); + NSArray *batchedContexts = [contexts subarrayWithRange:batchedRange]; + [self _layoutNodesFromContexts:batchedContexts ofKind:kind completion:completionBlock]; } } -- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { - NSAssert(NSThread.isMainThread, @"Main thread layout must be on the main thread."); +- (void)layoutLoadedNodes:(NSArray *)nodes fromContexts:(NSArray *)contexts ofKind:(NSString *)kind +{ + NSAssert(ASDisplayNodeThreadIsMain(), @"Layout of loaded nodes must happen on the main thread."); + ASDisplayNodeAssertTrue(nodes.count == contexts.count); - [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, __unused BOOL * stop) { - ASCellNode *node = nodes[idx]; - if (node.isNodeLoaded) { - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - [self _layoutNode:node withConstrainedSize:constrainedSize]; - } - }]; + [self _layoutNodes:nodes fromContexts:contexts atIndexesOfRange:NSMakeRange(0, nodes.count) ofKind:kind]; } /** @@ -147,27 +147,48 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; /** * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. */ -- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)_batchLayoutNodesFromContexts:(NSArray *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts ofKind:ASDataControllerRowNodeKind completion:^(NSArray *nodes, NSArray *indexPaths) { // Insert finished nodes into data storage [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; } -- (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +/** + * Perform measurement and layout of loaded or unloaded nodes based if they will be layed out on main thread or not + */ +- (void)_layoutNodes:(NSArray *)nodes fromContexts:(NSArray *)contexts atIndexesOfRange:(NSRange)range ofKind:(NSString *)kind { - if (!nodes.count) { + if (_dataSource == nil) { + return; + } + + // For any given layout pass that occurs, this method will be called at least twice, once on the main thread and + // the background, to result in complete coverage of both loaded and unloaded nodes + BOOL isMainThread = ASDisplayNodeThreadIsMain(); + for (NSUInteger k = range.location; k < NSMaxRange(range); k++) { + ASCellNode *node = nodes[k]; + // Only nodes that are loaded should be layout on the main thread + if (node.isNodeLoaded == isMainThread) { + ASIndexedNodeContext *context = contexts[k]; + [self _layoutNode:node withConstrainedSize:context.constrainedSize]; + } + } +} + +- (void)_layoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock +{ + if (!contexts.count || _dataSource == nil) { return; } - NSUInteger nodeCount = nodes.count; + NSUInteger nodeCount = contexts.count; NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:nodeCount]; dispatch_group_t layoutGroup = dispatch_group_create(); - ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodeCount); - for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { - NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j); + for (NSUInteger j = 0; j < nodeCount; j += kASDataControllerSizingCountPerProcessor) { + NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, nodeCount - j); __block NSArray *subarray; // Allocate nodes concurrently. @@ -176,13 +197,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(batchCount, queue, ^(size_t i) { unsigned long k = j + i; - ASCellNodeBlock cellBlock = nodes[k]; - ASCellNode *node = cellBlock(); - ASDisplayNodeAssertNotNil(node, @"Node block created nil node"); - allocatedNodeBuffer[i] = node; - if (!node.isNodeLoaded) { - nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; + ASCellNode *node = [contexts[k] allocateNode]; + if (node == nil) { + ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); + node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. } + allocatedNodeBuffer[i] = node; }); subarray = [[NSArray alloc] initWithObjects:allocatedNodeBuffer count:batchCount]; @@ -192,7 +212,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } free(allocatedNodeBuffer); }; - + + // Run the allocation block to concurrently create the cell nodes. Then, handle layout for nodes that are already loaded + // (e.g. the dataSource may have provided cells that have been used before), which must do layout on the main thread. + NSRange batchRange = NSMakeRange(0, batchCount); if (ASDisplayNodeThreadIsMain()) { dispatch_semaphore_t sema = dispatch_semaphore_create(0); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -200,33 +223,33 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; dispatch_semaphore_signal(sema); }); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); - [self layoutLoadedNodes:subarray ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + + [self _layoutNodes:subarray fromContexts:contexts atIndexesOfRange:batchRange ofKind:kind]; } else { allocationBlock(); [_mainSerialQueue performBlockOnMainThread:^{ - [self layoutLoadedNodes:subarray ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + [self _layoutNodes:subarray fromContexts:contexts atIndexesOfRange:batchRange ofKind:kind]; }]; } [allocatedNodes addObjectsFromArray:subarray]; dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - for (NSUInteger k = j; k < j + batchCount; k++) { - ASCellNode *node = allocatedNodes[k]; - // Only measure nodes whose views aren't loaded, since we're in the background. - // We should already have measured loaded nodes before we left the main thread, using layoutLoadedNodes:ofKind:atIndexPaths: - if (!node.isNodeLoaded) { - [self _layoutNode:node withConstrainedSize:nodeBoundSizes[k]]; - } - } + // We should already have measured loaded nodes before we left the main thread. Layout the remaining ones on a background thread. + NSRange asyncBatchRange = NSMakeRange(j, batchCount); + [self _layoutNodes:allocatedNodes fromContexts:contexts atIndexesOfRange:asyncBatchRange ofKind:kind]; }); } // Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated. dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); - free(nodeBoundSizes); - + if (completionBlock) { + NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodeCount]; + for (ASIndexedNodeContext *context in contexts) { + [indexPaths addObject:context.indexPath]; + } + completionBlock(allocatedNodes, indexPaths); } } @@ -238,17 +261,18 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - External Data Querying + Editing -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock { - if (indexPaths.count == 0) + if (!indexPaths.count || _dataSource == nil) { return; + } NSMutableArray *editingNodes = _editingNodes[kind]; ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); _editingNodes[kind] = editingNodes; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes); + NSMutableArray *completedNodes = ASTwoDimensionalArrayDeepMutableCopy(editingNodes); [_mainSerialQueue performBlockOnMainThread:^{ _completedNodes[kind] = completedNodes; @@ -258,13 +282,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; } -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock { - if (indexPaths.count == 0) { + if (!indexPaths.count || _dataSource == nil) { return; } - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); + LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForTwoDimensionalArray(_editingNodes[kind])); NSMutableArray *editingNodes = _editingNodes[kind]; ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; @@ -280,8 +304,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock { - if (indexSet.count == 0) + if (!indexSet.count|| _dataSource == nil) { return; + } if (_editingNodes[kind] == nil) { _editingNodes[kind] = [NSMutableArray array]; @@ -290,7 +315,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections); + NSArray *sectionsForCompleted = ASTwoDimensionalArrayDeepMutableCopy(sections); [_mainSerialQueue performBlockOnMainThread:^{ [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; @@ -302,8 +327,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock { - if (indexSet.count == 0) + if (!indexSet.count || _dataSource == nil) { return; + } + [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; [_mainSerialQueue performBlockOnMainThread:^{ [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; @@ -344,7 +371,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } /** - * Inserts sections, represented as arrays, into the backing store at the given indicies and notifies the delegate. + * Inserts sections, represented as arrays, into the backing store at the given indices and notifies the delegate. * * @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted * in the completed store on the main thread. The delegate is invoked on the main thread. @@ -358,7 +385,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } /** - * Removes sections at the given indicies from the backing store and notifies the delegate. + * Removes sections at the given indices from the backing store and notifies the delegate. * * @discussion Section array are first removed from the editing store, then the associated section in the completed * store is removed on the main thread. The delegate is invoked on the main thread. @@ -373,32 +400,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Initial Load & Full Reload (External API) -- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - [self accessDataSourceWithBlock:^{ - NSMutableArray *indexPaths = [NSMutableArray array]; - NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController: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 _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion]; @@ -411,15 +412,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion { + _initialReloadDataHasBeenCalled = YES; [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceSynchronously:synchronously withBlock:^{ NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; - NSMutableArray *updatedNodes = [NSMutableArray array]; - NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; + NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet]; // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; @@ -428,24 +429,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; LOG(@"Edit Transaction - reloadData"); // Remove everything that existed before the reload, now that we're ready to insert replacements - NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; - [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + NSUInteger editingNodesSectionCount = editingNodes.count; + + if (editingNodesSectionCount) { + NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)]; + [self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions]; + [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + } [self willReloadData]; - // Insert each section + // Insert empty sections 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 _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; if (completion) { dispatch_async(dispatch_get_main_queue(), completion); @@ -461,6 +463,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; } +- (void)waitUntilAllUpdatesAreCommitted +{ + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(_batchUpdateCounter == 0, @"Should not be called between beginUpdate or endUpdate"); + + // This should never be called in a batch update, return immediately therefore + if (_batchUpdateCounter > 0) { return; } + + [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + + // Schedule block in main serial queue to wait until all operations are finished that are + // where scheduled while waiting for the _editingTransactionQueue to finish + [_mainSerialQueue performBlockOnMainThread:^{ + ASDisplayNodeAssert(_editingTransactionQueue.operationCount == 0, @"No operation should be in the _editingTransactionQueue anymore"); + }]; +} + #pragma mark - Data Source Access (Calling _dataSource) /** @@ -489,43 +508,26 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } /** - * Fetches row nodes and their specified index paths for the provided sections from the data source. - * - * @discussion Results are stored in the passed mutable arrays. + * Fetches row contexts for the provided sections from the data source. */ -- (void)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet mutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths +- (NSArray *)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet { + NSMutableArray *contexts = [NSMutableArray array]; [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 nodeBlockAtIndexPath:indexPath]]; + ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; + [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock + indexPath:indexPath + constrainedSize:constrainedSize]]; } }]; + return contexts; } -/** - * Fetches row nodes and their specified index paths for all sections from the data source. - * - * @discussion Results are stored in the passed mutable arrays. - */ -- (void)_populateFromEntireDataSourceWithMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths -{ - NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController: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 nodeBlockAtIndexPath:indexPath]]; - } - } -} - - #pragma mark - Batching (External API) - (void)beginUpdates @@ -552,7 +554,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_mainSerialQueue performBlockOnMainThread:^{ // Deep copy _completedNodes to _externalCompletedNodes. // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. - _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); + _externalCompletedNodes = ASTwoDimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); [_delegate dataControllerBeginUpdates:self]; @@ -590,6 +592,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; { // 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 (!_initialReloadDataHasBeenCalled) { + return; + } + + // If we have never performed a reload, there is no value in executing edit operations as the initial + // reload will directly re-query the latest state of the datasource - so completely skip the block in this case. if (_batchUpdateCounter == 0) { block(); } else { @@ -607,9 +615,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; - NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections]; [self prepareForInsertSections:sections]; @@ -623,7 +629,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; }]; @@ -658,9 +665,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; - NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + NSArray *contexts= [self _populateFromDataSourceWithSectionIndexSet:sections]; [self prepareForReloadSections:sections]; @@ -669,12 +674,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); + LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForTwoDimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; // reinsert the elements - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; }]; @@ -764,18 +769,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; LOG(@"Edit Command - insertRows: %@", indexPaths); [_editingTransactionQueue waitUntilAllOperationsAreFinished]; - + + // sort indexPath to avoid messing up the index when inserting in several batches + NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; + NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + [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 nodeBlockAtIndexPath:sortedIndexPaths[i]]]; + for (NSIndexPath *indexPath in sortedIndexPaths) { + ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; + [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock + indexPath:indexPath + constrainedSize:constrainedSize]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; }]; @@ -810,20 +820,24 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // 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]; + NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; // FIXME: This doesn't currently do anything // FIXME: Shouldn't deletes be sorted in descending order? [indexPaths sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in indexPaths) { - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; + [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock + indexPath:indexPath + constrainedSize:constrainedSize]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; }]; @@ -878,12 +892,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]); - NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], @[indexPath]); + NSArray *indexPaths = @[indexPath]; [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; // Don't re-calculate size for moving - NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath]; + NSArray *newIndexPaths = @[newIndexPath]; [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; }]; }]; @@ -893,7 +907,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind { - return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil; + return _editingNodes[kind] != nil ? ASIndexPathsForTwoDimensionalArray(_editingNodes[kind]) : nil; } - (NSMutableArray *)editingNodesOfKind:(NSString *)kind @@ -969,7 +983,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSArray *)completedNodes { ASDisplayNodeAssertMainThread(); - return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind]; + return _externalCompletedNodes ? : _completedNodes[ASDataControllerRowNodeKind]; } #pragma mark - Dealloc diff --git a/AsyncDisplayKit/Details/ASEnvironment.h b/AsyncDisplayKit/Details/ASEnvironment.h new file mode 100644 index 0000000000..8c473cc08c --- /dev/null +++ b/AsyncDisplayKit/Details/ASEnvironment.h @@ -0,0 +1,98 @@ +/* + * 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" +#import "ASStackLayoutDefines.h" +#import "ASRelativeSize.h" + + +ASDISPLAYNODE_EXTERN_C_BEGIN +NS_ASSUME_NONNULL_BEGIN + +static const int kMaxEnvironmentStateBoolExtensions = 1; +static const int kMaxEnvironmentStateIntegerExtensions = 4; +static const int kMaxEnvironmentStateEdgeInsetExtensions = 1; + +#pragma mark - + +typedef struct ASEnvironmentStateExtensions { + // Values to store extensions + BOOL boolExtensions[kMaxEnvironmentStateBoolExtensions]; + NSInteger integerExtensions[kMaxEnvironmentStateIntegerExtensions]; + UIEdgeInsets edgeInsetsExtensions[kMaxEnvironmentStateEdgeInsetExtensions]; +} ASEnvironmentStateExtensions; + +#pragma mark - ASEnvironmentLayoutOptionsState + +typedef struct ASEnvironmentLayoutOptionsState { + CGFloat spacingBefore;// = 0; + CGFloat spacingAfter;// = 0; + BOOL flexGrow;// = NO; + BOOL flexShrink;// = NO; + ASRelativeDimension flexBasis;// = ASRelativeDimensionUnconstrained; + ASStackLayoutAlignSelf alignSelf;// = ASStackLayoutAlignSelfAuto; + CGFloat ascender;// = 0; + CGFloat descender;// = 0; + + ASRelativeSizeRange sizeRange;// = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(CGSizeZero), ASRelativeSizeMakeWithCGSize(CGSizeZero));; + CGPoint layoutPosition;// = CGPointZero; + + struct ASEnvironmentStateExtensions _extensions; +} ASEnvironmentLayoutOptionsState; + + +#pragma mark - ASEnvironmentHierarchyState + +typedef struct ASEnvironmentHierarchyState { + unsigned rasterized:1; // = NO + unsigned rangeManaged:1; // = NO + unsigned transitioningSupernodes:1; // = NO + unsigned layoutPending:1; // = NO +} ASEnvironmentHierarchyState; + + +#pragma mark - ASEnvironmentState + +typedef struct ASEnvironmentState { + struct ASEnvironmentHierarchyState hierarchyState; + struct ASEnvironmentLayoutOptionsState layoutOptionsState; +} ASEnvironmentState; +extern ASEnvironmentState ASEnvironmentStateMakeDefault(); + +ASDISPLAYNODE_EXTERN_C_END + + +#pragma mark - ASEnvironment + +/** + * ASEnvironment allows objects that conform to the ASEnvironment protocol to be able to propagate specific States + * defined in an ASEnvironmentState up and down the ASEnvironment tree. To be able to define how merges of + * States should happen, specific merge functions can be provided + */ +@protocol ASEnvironment + +/// The environment collection of an object which class conforms to the ASEnvironment protocol +- (ASEnvironmentState)environmentState; +- (void)setEnvironmentState:(ASEnvironmentState)environmentState; + +/// Returns the parent of an object which class conforms to the ASEnvironment protocol +- (id _Nullable)parent; + +/// Returns all children of an object which class conforms to the ASEnvironment protocol +- (nullable NSArray> *)children; + +/// Classes should implement this method and return YES / NO dependent if upward propagation is enabled or not +- (BOOL)supportsUpwardPropagation; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASEnvironment.m b/AsyncDisplayKit/Details/ASEnvironment.m new file mode 100644 index 0000000000..f3b2039f8f --- /dev/null +++ b/AsyncDisplayKit/Details/ASEnvironment.m @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import "ASEnvironment.h" + +ASEnvironmentLayoutOptionsState _ASEnvironmentLayoutOptionsStateMakeDefault() +{ + return (ASEnvironmentLayoutOptionsState) { + // Default values can be defined in here + }; +} + +ASEnvironmentHierarchyState _ASEnvironmentHierarchyStateMakeDefault() +{ + return (ASEnvironmentHierarchyState) { + // Default values can be defined in here + }; +} + +ASEnvironmentState ASEnvironmentStateMakeDefault() +{ + return (ASEnvironmentState) { + .layoutOptionsState = _ASEnvironmentLayoutOptionsStateMakeDefault(), + .hierarchyState = _ASEnvironmentHierarchyStateMakeDefault() + }; +} \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/AsyncDisplayKit/Details/ASFlowLayoutController.mm index dce8065e8e..500bc1ae51 100644 --- a/AsyncDisplayKit/Details/ASFlowLayoutController.mm +++ b/AsyncDisplayKit/Details/ASFlowLayoutController.mm @@ -10,6 +10,7 @@ #import "ASAssert.h" #import "ASDisplayNode.h" #import "ASIndexPath.h" +#import "CGRect+ASConvenience.h" #include #include @@ -48,29 +49,33 @@ - (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - CGFloat viewportScreenMetric; - ASScrollDirection leadingDirection; CGSize viewportSize = [self viewportSize]; + CGFloat viewportDirectionalSize = 0.0; + ASDirectionalScreenfulBuffer directionalBuffer = { 0, 0 }; + ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; + if (_layoutDirection == ASFlowLayoutDirectionHorizontal) { - ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || scrollDirection == ASScrollDirectionLeft || scrollDirection == ASScrollDirectionRight, @"Invalid scroll direction"); + ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || + scrollDirection == ASScrollDirectionLeft || + scrollDirection == ASScrollDirectionRight, @"Invalid scroll direction"); - viewportScreenMetric = viewportSize.width; - leadingDirection = ASScrollDirectionLeft; + viewportDirectionalSize = viewportSize.width; + directionalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters); } else { - ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || scrollDirection == ASScrollDirectionUp || scrollDirection == ASScrollDirectionDown, @"Invalid scroll direction"); + ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || + scrollDirection == ASScrollDirectionUp || + scrollDirection == ASScrollDirectionDown, @"Invalid scroll direction"); - viewportScreenMetric = viewportSize.height; - leadingDirection = ASScrollDirectionUp; + viewportDirectionalSize = viewportSize.height; + directionalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters); } - - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - CGFloat backScreens = scrollDirection == leadingDirection ? tuningParameters.leadingBufferScreenfuls : tuningParameters.trailingBufferScreenfuls; - CGFloat frontScreens = scrollDirection == leadingDirection ? tuningParameters.trailingBufferScreenfuls : tuningParameters.leadingBufferScreenfuls; - - ASIndexPath startPath = [self findIndexPathAtDistance:(-backScreens * viewportScreenMetric) fromIndexPath:_visibleRange.start]; - ASIndexPath endPath = [self findIndexPathAtDistance:(frontScreens * viewportScreenMetric) fromIndexPath:_visibleRange.end]; + ASIndexPath startPath = [self findIndexPathAtDistance:(-directionalBuffer.negativeDirection * viewportDirectionalSize) + fromIndexPath:_visibleRange.start]; + + ASIndexPath endPath = [self findIndexPathAtDistance:(directionalBuffer.positiveDirection * viewportDirectionalSize) + fromIndexPath:_visibleRange.end]; ASDisplayNodeAssert(startPath.section <= endPath.section, @"startPath should never begin at a further position than endPath"); diff --git a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.h b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.h index 736ff08753..6b5f7913ce 100644 --- a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.h +++ b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.h @@ -20,14 +20,14 @@ NS_ASSUME_NONNULL_BEGIN @param rects Array containing CGRects wrapped in NSValue. @param targetLayer The layer that the rects are relative to. The rects will be translated to the receiver's coordinate space when rendering. */ -- (id)initWithRects:(NSArray *)rects targetLayer:(nullable CALayer *)targetLayer; +- (instancetype)initWithRects:(NSArray *)rects targetLayer:(nullable CALayer *)targetLayer; /** @summary Initializes with CGRects for the highlighting, in the receiver's coordinate space. @param rects Array containing CGRects wrapped in NSValue. */ -- (id)initWithRects:(NSArray *)rects; +- (instancetype)initWithRects:(NSArray *)rects; @property (nullable, atomic, strong) __attribute__((NSObject)) CGColorRef highlightColor; @property (atomic, weak) CALayer *targetLayer; diff --git a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm index acaa97f00e..1308fa4557 100644 --- a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm +++ b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm @@ -49,12 +49,12 @@ static const UIEdgeInsets padding = {2, 4, 1.5, 4}; return (id)[NSNull null]; } -- (id)initWithRects:(NSArray *)rects +- (instancetype)initWithRects:(NSArray *)rects { return [self initWithRects:rects targetLayer:nil]; } -- (id)initWithRects:(NSArray *)rects targetLayer:(id)targetLayer +- (instancetype)initWithRects:(NSArray *)rects targetLayer:(id)targetLayer { if (self = [super init]) { _rects = [rects copy]; diff --git a/AsyncDisplayKit/Details/ASImageContainerProtocolCategories.h b/AsyncDisplayKit/Details/ASImageContainerProtocolCategories.h new file mode 100644 index 0000000000..dfe9e163c2 --- /dev/null +++ b/AsyncDisplayKit/Details/ASImageContainerProtocolCategories.h @@ -0,0 +1,19 @@ +// +// ASImageContainerProtocolCategories.h +// AsyncDisplayKit +// +// Created by Garrett Moon on 3/18/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +#import "ASImageProtocols.h" + +@interface UIImage (ASImageContainerProtocol) + +@end + +@interface NSData (ASImageContainerProtocol) + +@end diff --git a/AsyncDisplayKit/Details/ASImageContainerProtocolCategories.m b/AsyncDisplayKit/Details/ASImageContainerProtocolCategories.m new file mode 100644 index 0000000000..90b3da6f65 --- /dev/null +++ b/AsyncDisplayKit/Details/ASImageContainerProtocolCategories.m @@ -0,0 +1,37 @@ +// +// ASImageContainerProtocolCategories.m +// AsyncDisplayKit +// +// Created by Garrett Moon on 3/18/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASImageContainerProtocolCategories.h" + +@implementation UIImage (ASImageContainerProtocol) + +- (UIImage *)asdk_image +{ + return self; +} + +- (NSData *)asdk_animatedImageData +{ + return nil; +} + +@end + +@implementation NSData (ASImageContainerProtocol) + +- (UIImage *)asdk_image +{ + return nil; +} + +- (NSData *)asdk_animatedImageData +{ + return self; +} + +@end diff --git a/AsyncDisplayKit/Details/ASImageProtocols.h b/AsyncDisplayKit/Details/ASImageProtocols.h index 46811e88c6..3dc5568d06 100644 --- a/AsyncDisplayKit/Details/ASImageProtocols.h +++ b/AsyncDisplayKit/Details/ASImageProtocols.h @@ -7,16 +7,38 @@ */ #import -#import +#import NS_ASSUME_NONNULL_BEGIN -typedef void(^ASImageCacherCompletion)(UIImage * _Nullable imageFromCache); +@protocol ASAnimatedImageProtocol; + +@protocol ASImageContainerProtocol + +- (UIImage *)asdk_image; +- (NSData *)asdk_animatedImageData; + +@end + +typedef void(^ASImageCacherCompletion)(id _Nullable imageFromCache); @protocol ASImageCacheProtocol @optional +/** + @abstract Attempts to fetch an image with the given URL from a memory cache. + @param URL The URL of the image to retrieve from the cache. + @discussion This method exists to support synchronous rendering of nodes. Before the layer is drawn, this method + is called to attempt to get the image out of the cache synchronously. This allows drawing to occur on the main thread + if displaysAsynchronously is set to NO or recursivelyEnsureDisplaySynchronously: has been called. + + If `URL` is nil, `completion` will be invoked immediately with a nil image. This method *should* block + the calling thread to fetch the image from a fast memory cache. It is OK to return nil from this method and instead + support only cachedImageWithURL:callbackQueue:completion: however, synchronous rendering will not be possible. + */ +- (nullable id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL; + /** @abstract Attempts to fetch an image with the given URL from the cache. @param URL The URL of the image to retrieve from the cache. @@ -39,7 +61,7 @@ typedef void(^ASImageCacherCompletion)(UIImage * _Nullable imageFromCache); @end -typedef void(^ASImageDownloaderCompletion)(UIImage * _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier); +typedef void(^ASImageDownloaderCompletion)(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier); typedef void(^ASImageDownloaderProgress)(CGFloat progress); typedef void(^ASImageDownloaderProgressImage)(UIImage *progressImage, id _Nullable downloadIdentifier); @@ -63,6 +85,12 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { @optional +/** + @abstract Return an object that conforms to ASAnimatedImageProtocol + @param animatedImageData Data that represents an animated image. + */ +- (nullable id )animatedImageWithData:(NSData *)animatedImageData; + //You must implement the following method OR the deprecated method at the bottom /** @@ -106,6 +134,27 @@ withDownloadIdentifier:(id)downloadIdentifier; @end +@protocol ASAnimatedImageProtocol + +@property (nonatomic, strong, readwrite) void (^coverImageReadyCallback)(UIImage *coverImage); + +@required + +@property (nonatomic, readonly) UIImage *coverImage; +@property (nonatomic, readonly) BOOL coverImageReady; +@property (nonatomic, readonly) CFTimeInterval totalDuration; +@property (nonatomic, readonly) NSUInteger frameInterval; +@property (nonatomic, readonly) size_t loopCount; +@property (nonatomic, readonly) size_t frameCount; +@property (nonatomic, readonly) BOOL playbackReady; +@property (nonatomic, strong, readwrite) dispatch_block_t playbackReadyCallback; + +- (CGImageRef)imageAtIndex:(NSUInteger)index; +- (CFTimeInterval)durationAtIndex:(NSUInteger)index; +- (void)clearAnimatedImageCache; + +@end + @protocol ASImageDownloaderProtocolDeprecated @optional @@ -115,7 +164,7 @@ withDownloadIdentifier:(id)downloadIdentifier; - (nullable id)downloadImageWithURL:(NSURL *)URL callbackQueue:(nullable dispatch_queue_t)callbackQueue downloadProgressBlock:(void (^ _Nullable)(CGFloat progress))downloadProgressBlock - completion:(void (^ _Nullable)(CGImageRef _Nullable image, NSError * _Nullable error))completion; + completion:(void (^ _Nullable)(CGImageRef _Nullable image, NSError * _Nullable error))completion ASDISPLAYNODE_DEPRECATED; @end @@ -127,7 +176,7 @@ withDownloadIdentifier:(id)downloadIdentifier; */ - (void)fetchCachedImageWithURL:(nullable NSURL *)URL callbackQueue:(nullable dispatch_queue_t)callbackQueue - completion:(void (^)(CGImageRef _Nullable imageFromCache))completion; + completion:(void (^)(CGImageRef _Nullable imageFromCache))completion ASDISPLAYNODE_DEPRECATED; @end diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.h b/AsyncDisplayKit/Details/ASIndexedNodeContext.h new file mode 100644 index 0000000000..ed1d2f49ba --- /dev/null +++ b/AsyncDisplayKit/Details/ASIndexedNodeContext.h @@ -0,0 +1,25 @@ +// +// ASIndexedNodeContext.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 2/28/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface ASIndexedNodeContext : NSObject + +@property (nonatomic, readonly, strong) NSIndexPath *indexPath; +@property (nonatomic, readonly, assign) ASSizeRange constrainedSize; + +- (instancetype)initWithNodeBlock:(ASCellNodeBlock)nodeBlock + indexPath:(NSIndexPath *)indexPath + constrainedSize:(ASSizeRange)constrainedSize; + +/** + * Returns a node allocated by executing node block. Node block will be nil out immediately. + */ +- (ASCellNode *)allocateNode; + +@end diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.m b/AsyncDisplayKit/Details/ASIndexedNodeContext.m new file mode 100644 index 0000000000..b6038137d6 --- /dev/null +++ b/AsyncDisplayKit/Details/ASIndexedNodeContext.m @@ -0,0 +1,42 @@ +// +// ASIndexedNodeContext.m +// AsyncDisplayKit +// +// Created by Huy Nguyen on 2/28/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASIndexedNodeContext.h" + +@interface ASIndexedNodeContext () + +/// Required node block used to allocate a cell node. Nil after the first execution. +@property (nonatomic, strong) ASCellNodeBlock nodeBlock; + +@end + +@implementation ASIndexedNodeContext + +- (instancetype)initWithNodeBlock:(ASCellNodeBlock)nodeBlock + indexPath:(NSIndexPath *)indexPath + constrainedSize:(ASSizeRange)constrainedSize; +{ + NSAssert(nodeBlock != nil && indexPath != nil, @"Node block and index path must not be nil"); + self = [super init]; + if (self) { + _nodeBlock = nodeBlock; + _indexPath = indexPath; + _constrainedSize = constrainedSize; + } + return self; +} + +- (ASCellNode *)allocateNode +{ + NSAssert(_nodeBlock != nil, @"Node block is gone. Should not execute it more than once"); + ASCellNode *node = _nodeBlock(); + _nodeBlock = nil; + return node; +} + +@end diff --git a/AsyncDisplayKit/Details/ASLayoutRangeType.h b/AsyncDisplayKit/Details/ASLayoutRangeType.h index aed3d98bb9..1b1e3a3c16 100644 --- a/AsyncDisplayKit/Details/ASLayoutRangeType.h +++ b/AsyncDisplayKit/Details/ASLayoutRangeType.h @@ -20,12 +20,29 @@ typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) { * Range controller can automatically switch to full mode when conditions change. */ ASLayoutRangeModeMinimum = 0, + /** * Normal/Full mode that a range controller uses to provide the best experience for end users. * This mode is usually used for an active scroll view. * A range controller under this requires more resources compare to minimum mode. */ ASLayoutRangeModeFull, + + /** + * Visible Only mode is used when a range controller should set its display and fetch data regions to only the size of their bounds. + * This causes all additional backing stores & fetched data to be released, while ensuring a user revisiting the view will + * still be able to see the expected content. This mode is automatically set on all ASRangeControllers when the app suspends, + * allowing the operating system to keep the app alive longer and increase the chance it is still warm when the user returns. + */ + ASLayoutRangeModeVisibleOnly, + + /** + * Low Memory mode is used when a range controller should discard ALL graphics buffers, including for the area that would be visible + * the next time the user views it (bounds). The only range it preserves is Fetch Data, which is limited to the bounds, allowing + * the content to be restored relatively quickly by re-decoding images (the compressed images are ~10% the size of the decoded ones, + * and text is a tiny fraction of its rendered size). + */ + ASLayoutRangeModeLowMemory, ASLayoutRangeModeCount }; diff --git a/AsyncDisplayKit/Details/ASMainSerialQueue.mm b/AsyncDisplayKit/Details/ASMainSerialQueue.mm index f9ce3481c5..3112757af0 100644 --- a/AsyncDisplayKit/Details/ASMainSerialQueue.mm +++ b/AsyncDisplayKit/Details/ASMainSerialQueue.mm @@ -9,6 +9,7 @@ #import "ASMainSerialQueue.h" #import "ASThread.h" +#import "ASInternalHelpers.h" @interface ASMainSerialQueue () { @@ -45,7 +46,7 @@ ASDN::MutexLocker l(_serialQueueLock); dispatch_block_t block; if (_blocks.count > 0) { - block = [_blocks objectAtIndex:0]; + block = _blocks[0]; [_blocks removeObjectAtIndex:0]; } else { break; @@ -55,13 +56,12 @@ } while (true); }; - if ([NSThread isMainThread]) { - mainThread(); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - mainThread(); - }); - } + ASPerformBlockOnMainThread(mainThread); +} + +- (NSString *)description +{ + return [[super description] stringByAppendingFormat:@" Blocks: %@", _blocks]; } @end diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h index 1a7850b2fa..12c6b27376 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h @@ -1,9 +1,9 @@ // // ASPINRemoteImageDownloader.h -// Pods +// AsyncDisplayKit // // Created by Garrett Moon on 2/5/16. -// +// Copyright © 2016 Facebook. All rights reserved. // #import diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m index dc15fb98ec..1716006f5f 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -1,9 +1,9 @@ // // ASPINRemoteImageDownloader.m -// Pods +// AsyncDisplayKit // // Created by Garrett Moon on 2/5/16. -// +// Copyright © 2016 Facebook. All rights reserved. // #ifdef PIN_REMOTE_IMAGE @@ -11,27 +11,104 @@ #import "ASAssert.h" #import "ASThread.h" +#import "ASImageContainerProtocolCategories.h" + +#if __has_include ("PINAnimatedImage.h") +#define PIN_ANIMATED_AVAILABLE 1 +#import "PINAnimatedImage.h" +#import +#else +#define PIN_ANIMATED_AVAILABLE 0 +#endif #import +#import #import +#if PIN_ANIMATED_AVAILABLE +@interface ASPINRemoteImageDownloader () + +@end + +@interface PINAnimatedImage (ASPINRemoteImageDownloader) + +@end + +@implementation PINAnimatedImage (ASPINRemoteImageDownloader) + +- (void)setCoverImageReadyCallback:(void (^)(UIImage * _Nonnull))coverImageReadyCallback +{ + self.infoCompletion = coverImageReadyCallback; +} + +- (void (^)(UIImage * _Nonnull))coverImageReadyCallback +{ + return self.infoCompletion; +} + +- (void)setPlaybackReadyCallback:(dispatch_block_t)playbackReadyCallback +{ + self.fileReady = playbackReadyCallback; +} + +- (dispatch_block_t)playbackReadyCallback +{ + return self.fileReady; +} + +@end +#endif + @implementation ASPINRemoteImageDownloader + (instancetype)sharedDownloader { static ASPINRemoteImageDownloader *sharedDownloader = nil; - static dispatch_once_t once = 0; - dispatch_once(&once, ^{ + static dispatch_once_t onceToken = 0; + dispatch_once(&onceToken, ^{ sharedDownloader = [[ASPINRemoteImageDownloader alloc] init]; }); return sharedDownloader; } +- (PINRemoteImageManager *)sharedPINRemoteImageManager +{ + static PINRemoteImageManager *sharedPINRemoteImageManager = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ +#if PIN_ANIMATED_AVAILABLE + sharedPINRemoteImageManager = [[PINRemoteImageManager alloc] initWithSessionConfiguration:nil alternativeRepresentationProvider:self]; +#else + sharedPINRemoteImageManager = [[PINRemoteImageManager alloc] initWithSessionConfiguration:nil]; +#endif + }); + return sharedPINRemoteImageManager; +} + #pragma mark ASImageProtocols -- (void)fetchCachedImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - completion:(void (^)(CGImageRef imageFromCache))completion +#if PIN_ANIMATED_AVAILABLE +- (nullable id )animatedImageWithData:(NSData *)animatedImageData +{ + return [[PINAnimatedImage alloc] initWithAnimatedImageData:animatedImageData]; +} +#endif + +- (id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL; +{ + NSString *key = [[self sharedPINRemoteImageManager] cacheKeyForURL:URL processorKey:nil]; + PINRemoteImageManagerResult *result = [[self sharedPINRemoteImageManager] synchronousImageFromCacheWithCacheKey:key options:PINRemoteImageManagerDownloadOptionsSkipDecode]; +#if PIN_ANIMATED_AVAILABLE + if (result.alternativeRepresentation) { + return result.alternativeRepresentation; + } +#endif + return result.image; +} + +- (void)cachedImageWithURL:(NSURL *)URL + callbackQueue:(dispatch_queue_t)callbackQueue + completion:(ASImageCacherCompletion)completion { // We do not check the cache here and instead check it in downloadImageWithURL to avoid checking the cache twice. // If we're targeting the main queue and we're on the main thread, complete immediately. @@ -46,23 +123,39 @@ - (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL { - PINRemoteImageManager *manager = [PINRemoteImageManager sharedImageManager]; + PINRemoteImageManager *manager = [self sharedPINRemoteImageManager]; NSString *key = [manager cacheKeyForURL:URL processorKey:nil]; [[[manager cache] memoryCache] removeObjectForKey:key]; } - (nullable id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(void (^)(CGFloat progress))downloadProgressBlock - completion:(void (^)(UIImage *image, NSError * error, id downloadIdentifier))completion + downloadProgress:(ASImageDownloaderProgress)downloadProgress + completion:(ASImageDownloaderCompletion)completion; { - return [[PINRemoteImageManager sharedImageManager] downloadImageWithURL:URL options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult *result) { + return [[self sharedPINRemoteImageManager] downloadImageWithURL:URL options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult *result) { /// If we're targeting the main queue and we're on the main thread, complete immediately. if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { - completion(result.image, result.error, result.UUID); +#if PIN_ANIMATED_AVAILABLE + if (result.alternativeRepresentation) { + completion(result.alternativeRepresentation, result.error, result.UUID); + } else { + completion(result.image, result.error, result.UUID); + } +#else + completion(result.image, result.error, result.UUID); +#endif } else { dispatch_async(callbackQueue, ^{ +#if PIN_ANIMATED_AVAILABLE + if (result.alternativeRepresentation) { + completion(result.alternativeRepresentation, result.error, result.UUID); + } else { + completion(result.image, result.error, result.UUID); + } +#else completion(result.image, result.error, result.UUID); +#endif }); } }]; @@ -71,7 +164,7 @@ - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier { ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[PINRemoteImageManager sharedImageManager] cancelTaskWithUUID:downloadIdentifier]; + [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier]; } - (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier @@ -79,13 +172,13 @@ ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); if (progressBlock) { - [[PINRemoteImageManager sharedImageManager] setProgressImageCallback:^(PINRemoteImageManagerResult * _Nonnull result) { + [[self sharedPINRemoteImageManager] setProgressImageCallback:^(PINRemoteImageManagerResult * _Nonnull result) { dispatch_async(callbackQueue, ^{ progressBlock(result.image, result.UUID); }); } ofTaskWithUUID:downloadIdentifier]; } else { - [[PINRemoteImageManager sharedImageManager] setProgressImageCallback:nil ofTaskWithUUID:downloadIdentifier]; + [[self sharedPINRemoteImageManager] setProgressImageCallback:nil ofTaskWithUUID:downloadIdentifier]; } } @@ -107,7 +200,19 @@ pi_priority = PINRemoteImageManagerPriorityVeryHigh; break; } - [[PINRemoteImageManager sharedImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier]; + [[self sharedPINRemoteImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier]; +} + +#pragma mark - PINRemoteImageManagerAlternateRepresentationProvider + +- (id)alternateRepresentationWithData:(NSData *)data options:(PINRemoteImageManagerDownloadOptions)options +{ +#if PIN_ANIMATED_AVAILABLE + if ([data pin_isGIF]) { + return data; + } +#endif + return nil; } @end diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index d5288f40e2..bba570d0e6 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -11,8 +11,9 @@ #import #import #import +#import -#define RangeControllerLoggingEnabled 0 +#define ASRangeControllerLoggingEnabled 0 NS_ASSUME_NONNULL_BEGIN @@ -49,7 +50,7 @@ NS_ASSUME_NONNULL_BEGIN * * @param contentView UIView to add a (sized) node's view to. * - * @param cellNode The cell node to be added. + * @param node The cell node to be added. */ - (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node; @@ -57,6 +58,11 @@ NS_ASSUME_NONNULL_BEGIN - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; +// These methods call the corresponding method on each node, visiting each one that +// the range controller has set a non-default interface state on. +- (void)clearContents; +- (void)clearFetchedData; + /** * An object that describes the layout behavior of the ranged component (table view, collection view, etc.) * @@ -77,6 +83,7 @@ NS_ASSUME_NONNULL_BEGIN @end + /** * Data source for ASRangeController. * diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 1b8d7d8f89..7935564a3d 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -9,6 +9,7 @@ #import "ASRangeController.h" #import "ASAssert.h" +#import "ASWeakSet.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNodeInternal.h" #import "ASMultiDimensionalArrayUtils.h" @@ -23,14 +24,19 @@ ASScrollDirection _scrollDirection; NSSet *_allPreviousIndexPaths; ASLayoutRangeMode _currentRangeMode; + BOOL _didUpdateCurrentRange; BOOL _didRegisterForNotifications; CFAbsoluteTime _pendingDisplayNodesTimestamp; } @end +static UIApplicationState __ApplicationState = UIApplicationStateActive; + @implementation ASRangeController +#pragma mark - Lifecycle + - (instancetype)init { if (!(self = [super init])) { @@ -39,6 +45,9 @@ _rangeIsValid = YES; _currentRangeMode = ASLayoutRangeModeInvalid; + _didUpdateCurrentRange = NO; + + [[[self class] allRangeControllersWeakSet] addObject:self]; return self; } @@ -50,13 +59,18 @@ } } -#pragma mark - Core visible node range managment API +#pragma mark - Core visible node range management API + ++ (BOOL)isFirstRangeUpdateForRangeMode:(ASLayoutRangeMode)rangeMode +{ + return (rangeMode == ASLayoutRangeModeInvalid); +} + (ASLayoutRangeMode)rangeModeForInterfaceState:(ASInterfaceState)interfaceState currentRangeMode:(ASLayoutRangeMode)currentRangeMode { BOOL isVisible = (ASInterfaceStateIncludesVisible(interfaceState)); - BOOL isFirstRangeUpdate = (currentRangeMode == ASLayoutRangeModeInvalid); + BOOL isFirstRangeUpdate = [self isFirstRangeUpdateForRangeMode:currentRangeMode]; if (!isVisible || isFirstRangeUpdate) { return ASLayoutRangeModeMinimum; } @@ -64,58 +78,102 @@ return ASLayoutRangeModeFull; } +- (ASInterfaceState)interfaceState +{ + ASInterfaceState selfInterfaceState = ASInterfaceStateNone; + if (_dataSource) { + selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; + } + if (__ApplicationState == UIApplicationStateBackground) { + // If the app is background, pretend to be invisible so that we inform each cell it is no longer being viewed by the user + selfInterfaceState &= ~(ASInterfaceStateVisible); + } + return selfInterfaceState; +} + - (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection { _scrollDirection = scrollDirection; + + // Perform update immediately, so that cells receive a visibilityDidChange: call before their first pixel is visible. [self scheduleRangeUpdate]; } +- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode +{ + if (_currentRangeMode != rangeMode) { + _currentRangeMode = rangeMode; + _didUpdateCurrentRange = YES; + + [self scheduleRangeUpdate]; + } +} + - (void)scheduleRangeUpdate { if (_queuedRangeUpdate) { return; } - + // coalesce these events -- handling them multiple times per runloop is noisy and expensive _queuedRangeUpdate = YES; dispatch_async(dispatch_get_main_queue(), ^{ - [self _updateVisibleNodeIndexPaths]; + [self performRangeUpdate]; }); } +- (void)performRangeUpdate +{ + // Call this version if you want the update to occur immediately, such as on app suspend, as another runloop may not occur. + ASDisplayNodeAssertMainThread(); + _queuedRangeUpdate = YES; // For now, set this flag as _update... expects it and clears it. + [self _updateVisibleNodeIndexPaths]; +} + - (void)setLayoutController:(id)layoutController { _layoutController = layoutController; _layoutControllerImplementsSetVisibleIndexPaths = [_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]; + if (_layoutController && _queuedRangeUpdate) { + [self performRangeUpdate]; + } +} + +- (void)setDataSource:(id)dataSource +{ + _dataSource = dataSource; + if (_dataSource && _queuedRangeUpdate) { + [self performRangeUpdate]; + } } - (void)_updateVisibleNodeIndexPaths { ASDisplayNodeAssert(_layoutController, @"An ASLayoutController is required by ASRangeController"); - if (!_queuedRangeUpdate || !_layoutController) { + if (!_queuedRangeUpdate || !_layoutController || !_dataSource) { return; } - - // TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges - // Example: ... = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible]; - NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self]; - - if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... - _queuedRangeUpdate = NO; - return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later - } - - [_layoutController setViewportSize:[_dataSource viewportSizeForRangeController:self]]; - - // the layout controller needs to know what the current visible indices are to calculate range offsets - if (_layoutControllerImplementsSetVisibleIndexPaths) { - [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; - } // allNodes is a 2D array: it contains arrays for each section, each containing nodes. NSArray *allNodes = [_dataSource completedNodes]; NSUInteger numberOfSections = [allNodes count]; + + // TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges + // Example: ... = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible]; + NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self]; + + if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... + _queuedRangeUpdate = NO; + return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later + } + + [_layoutController setViewportSize:[_dataSource viewportSizeForRangeController:self]]; + + // the layout controller needs to know what the current visible indices are to calculate range offsets + if (_layoutControllerImplementsSetVisibleIndexPaths) { + [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; + } NSArray *currentSectionNodes = nil; NSInteger currentSectionIndex = -1; // Set to -1 so we don't match any indexPath.section on the first iteration. @@ -129,9 +187,13 @@ // the network or display queues before preloading (offscreen) nodes are enqueued. NSMutableOrderedSet *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths]; - ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; - ASLayoutRangeMode rangeMode = [ASRangeController rangeModeForInterfaceState:selfInterfaceState - currentRangeMode:_currentRangeMode]; + ASInterfaceState selfInterfaceState = [self interfaceState]; + ASLayoutRangeMode rangeMode = _currentRangeMode; + // If the range mode is explicitly set via updateCurrentRangeWithMode: it will last in that mode until the + // range controller becomes visible again or explicitly changes the range mode again + if ((!_didUpdateCurrentRange && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) { + rangeMode = [ASRangeController rangeModeForInterfaceState:selfInterfaceState currentRangeMode:_currentRangeMode]; + } ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeMode:rangeMode rangeType:ASLayoutRangeTypeFetchData]; @@ -142,10 +204,12 @@ rangeMode:rangeMode rangeType:ASLayoutRangeTypeFetchData]; } - + ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay]; - if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, ASRangeTuningParametersZero)) { + if (rangeMode == ASLayoutRangeModeLowMemory) { + displayIndexPaths = [NSSet set]; + } else if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, ASRangeTuningParametersZero)) { displayIndexPaths = visibleIndexPaths; } else if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, parametersFetchData)) { displayIndexPaths = fetchDataIndexPaths; @@ -168,18 +232,21 @@ NSSet *allCurrentIndexPaths = [[allIndexPaths set] copy]; [allIndexPaths unionSet:_allPreviousIndexPaths]; _allPreviousIndexPaths = allCurrentIndexPaths; + _currentRangeMode = rangeMode; + _didUpdateCurrentRange = NO; if (!_rangeIsValid) { - [allIndexPaths addObjectsFromArray:ASIndexPathsForMultidimensionalArray(allNodes)]; + [allIndexPaths addObjectsFromArray:ASIndexPathsForTwoDimensionalArray(allNodes)]; } - + // TODO Don't register for notifications if this range update doesn't cause any node to enter rendering pipeline. // This can be done once there is an API to observe to (or be notified upon) interface state changes or pipeline enterings [self registerForNotificationsForInterfaceStateIfNeeded:selfInterfaceState]; -#if RangeControllerLoggingEnabled - NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil); +#if ASRangeControllerLoggingEnabled + ASDisplayNodeAssertTrue([visibleIndexPaths isSubsetOfSet:displayIndexPaths]); + NSMutableArray *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil); #endif for (NSIndexPath *indexPath in allIndexPaths) { @@ -188,27 +255,34 @@ ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout; if (ASInterfaceStateIncludesVisible(selfInterfaceState)) { - if ([fetchDataIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStateFetchData; - } - if ([displayIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStateDisplay; - } if ([visibleIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStateVisible; + interfaceState |= (ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData); + } else { + if ([fetchDataIndexPaths containsObject:indexPath]) { + interfaceState |= ASInterfaceStateFetchData; + } + if ([displayIndexPaths containsObject:indexPath]) { + interfaceState |= ASInterfaceStateDisplay; + } } } else { // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the // instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet. // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:. - - // Set Layout, Fetch Data, Display. DO NOT set Visible: even though these elements are in the visible range / "viewport", - // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above. + if ([allCurrentIndexPaths containsObject:indexPath]) { - // We might be looking at an indexPath that was previously in-range, but now we need to clear it. - // In that case we'll just set it back to MeasureLayout. Only set Display | FetchData if in allCurrentIndexPaths. - interfaceState |= ASInterfaceStateDisplay; + // DO NOT set Visible: even though these elements are in the visible range / "viewport", + // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above + + // Set Layout, Fetch Data interfaceState |= ASInterfaceStateFetchData; + + if (rangeMode != ASLayoutRangeModeLowMemory) { + // Add Display. + // We might be looking at an indexPath that was previously in-range, but now we need to clear it. + // In that case we'll just set it back to MeasureLayout. Only set Display | FetchData if in allCurrentIndexPaths. + interfaceState |= ASInterfaceStateDisplay; + } } } @@ -219,18 +293,18 @@ if (section != currentSectionIndex) { // Often we'll be dealing with indexPaths in the same section, but the set isn't sorted and we may even bounce // between the same ones. Still, this saves dozens of method calls to access the inner array and count. - currentSectionNodes = [allNodes objectAtIndex:section]; + currentSectionNodes = allNodes[section]; numberOfNodesInSection = [currentSectionNodes count]; currentSectionIndex = section; } if (row < numberOfNodesInSection) { - ASDisplayNode *node = [currentSectionNodes objectAtIndex:row]; + ASDisplayNode *node = currentSectionNodes[row]; ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. if (node.interfaceState != interfaceState) { -#if RangeControllerLoggingEnabled +#if ASRangeControllerLoggingEnabled [modifiedIndexPaths addObject:indexPath]; #endif [node recursivelySetInterfaceState:interfaceState]; @@ -238,33 +312,24 @@ } } } - + if (_didRegisterForNotifications) { _pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent(); } - + _rangeIsValid = YES; _queuedRangeUpdate = NO; -#if RangeControllerLoggingEnabled - NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; - BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; - NSLog(@"visible sets are equal: %d", setsAreEqual); - if (!setsAreEqual) { - NSLog(@"standard: %@", visibleIndexPaths); - NSLog(@"custom: %@", visibleNodePathsSet); - } - +#if ASRangeControllerLoggingEnabled +// NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; +// BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; +// NSLog(@"visible sets are equal: %d", setsAreEqual); +// if (!setsAreEqual) { +// NSLog(@"standard: %@", visibleIndexPaths); +// NSLog(@"custom: %@", visibleNodePathsSet); +// } [modifiedIndexPaths sortUsingSelector:@selector(compare:)]; - - for (NSIndexPath *indexPath in modifiedIndexPaths) { - ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; - ASInterfaceState interfaceState = node.interfaceState; - BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState); - BOOL inDisplay = ASInterfaceStateIncludesDisplay(interfaceState); - BOOL inFetchData = ASInterfaceStateIncludesFetchData(interfaceState); - NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, inVisible, inDisplay, inFetchData); - } + NSLog(@"Range update complete; modifiedIndexPaths: %@", [self descriptionWithIndexPaths:modifiedIndexPaths]); #endif } @@ -274,7 +339,7 @@ { if (!_didRegisterForNotifications) { ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState - currentRangeMode:_currentRangeMode]; + currentRangeMode:_currentRangeMode]; if (_currentRangeMode != nextRangeMode) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scheduledNodesDidDisplay:) @@ -287,7 +352,7 @@ - (void)scheduledNodesDidDisplay:(NSNotification *)notification { - CFAbsoluteTime notificationTimestamp = ((NSNumber *)[notification.userInfo objectForKey:ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp]).doubleValue; + CFAbsoluteTime notificationTimestamp = ((NSNumber *) notification.userInfo[ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp]).doubleValue; if (_pendingDisplayNodesTimestamp < notificationTimestamp) { // The rendering engine has processed all the nodes this range controller scheduled. Let's schedule a range update [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; @@ -378,4 +443,132 @@ }); } -@end \ No newline at end of file +#pragma mark - Memory Management + +// Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. +- (void)clearContents +{ + for (NSArray *section in [_dataSource completedNodes]) { + for (ASDisplayNode *node in section) { + if (ASInterfaceStateIncludesDisplay(node.interfaceState)) { + [node exitInterfaceState:ASInterfaceStateDisplay]; + } + } + } +} + +- (void)clearFetchedData +{ + for (NSArray *section in [_dataSource completedNodes]) { + for (ASDisplayNode *node in section) { + if (ASInterfaceStateIncludesFetchData(node.interfaceState)) { + [node exitInterfaceState:ASInterfaceStateFetchData]; + } + } + } +} + +#pragma mark - Class Methods (Application Notification Handlers) + ++ (ASWeakSet *)allRangeControllersWeakSet +{ + static ASWeakSet *__allRangeControllersWeakSet; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + __allRangeControllersWeakSet = [[ASWeakSet alloc] init]; + [self registerSharedApplicationNotifications]; + }); + return __allRangeControllersWeakSet; +} + ++ (void)registerSharedApplicationNotifications +{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; +#if ASRangeControllerAutomaticLowMemoryHandling + [center addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; +#endif + [center addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [center addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; +} + +static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeVisibleOnly; ++ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode +{ + ASDisplayNodeAssert(rangeMode == ASLayoutRangeModeVisibleOnly || rangeMode == ASLayoutRangeModeLowMemory, @"It is highly inadvisable to engage a larger range mode when a memory warning occurs, as this will almost certainly cause app eviction"); + __rangeModeForMemoryWarnings = rangeMode; +} + ++ (void)didReceiveMemoryWarning:(NSNotification *)notification +{ + NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects]; + for (ASRangeController *rangeController in allRangeControllers) { + BOOL isDisplay = ASInterfaceStateIncludesDisplay([rangeController interfaceState]); + [rangeController updateCurrentRangeWithMode:isDisplay ? ASLayoutRangeModeMinimum : __rangeModeForMemoryWarnings]; + [rangeController performRangeUpdate]; + } + +#if ASRangeControllerLoggingEnabled + NSLog(@"+[ASRangeController didReceiveMemoryWarning] with controllers: %@", allRangeControllers); +#endif +} + ++ (void)didEnterBackground:(NSNotification *)notification +{ + NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects]; + for (ASRangeController *rangeController in allRangeControllers) { + // We do not want to fully collapse the Display ranges of any visible range controllers so that flashes can be avoided when + // the app is resumed. Non-visible controllers can be more aggressively culled to the LowMemory state (see definitions for documentation) + BOOL isVisible = ASInterfaceStateIncludesVisible([rangeController interfaceState]); + [rangeController updateCurrentRangeWithMode:isVisible ? ASLayoutRangeModeVisibleOnly : ASLayoutRangeModeLowMemory]; + } + + // Because -interfaceState checks __ApplicationState and always clears the "visible" bit if Backgrounded, we must set this after updating the range mode. + __ApplicationState = UIApplicationStateBackground; + for (ASRangeController *rangeController in allRangeControllers) { + // Trigger a range update immediately, as we may not be allowed by the system to run the update block scheduled by changing range mode. + [rangeController performRangeUpdate]; + } + +#if ASRangeControllerLoggingEnabled + NSLog(@"+[ASRangeController didEnterBackground] with controllers, after backgrounding: %@", allRangeControllers); +#endif +} + ++ (void)willEnterForeground:(NSNotification *)notification +{ + NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects]; + __ApplicationState = UIApplicationStateActive; + for (ASRangeController *rangeController in allRangeControllers) { + BOOL isVisible = ASInterfaceStateIncludesVisible([rangeController interfaceState]); + [rangeController updateCurrentRangeWithMode:isVisible ? ASLayoutRangeModeMinimum : ASLayoutRangeModeVisibleOnly]; + [rangeController performRangeUpdate]; + } + +#if ASRangeControllerLoggingEnabled + NSLog(@"+[ASRangeController willEnterForeground] with controllers, after foregrounding: %@", allRangeControllers); +#endif +} + +#pragma mark - Debugging + +- (NSString *)descriptionWithIndexPaths:(NSArray *)indexPaths +{ + NSMutableString *description = [NSMutableString stringWithFormat:@"%@ %@", [super description], @" allPreviousIndexPaths:\n"]; + for (NSIndexPath *indexPath in indexPaths) { + ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; + ASInterfaceState interfaceState = node.interfaceState; + BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState); + BOOL inDisplay = ASInterfaceStateIncludesDisplay(interfaceState); + BOOL inFetchData = ASInterfaceStateIncludesFetchData(interfaceState); + [description appendFormat:@"indexPath %@, Visible: %d, Display: %d, FetchData: %d\n", indexPath, inVisible, inDisplay, inFetchData]; + } + return description; +} + +- (NSString *)description +{ + NSArray *indexPaths = [[_allPreviousIndexPaths allObjects] sortedArrayUsingSelector:@selector(compare:)]; + return [self descriptionWithIndexPaths:indexPaths]; +} + +@end diff --git a/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h b/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h new file mode 100644 index 0000000000..4f34cd1cef --- /dev/null +++ b/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h @@ -0,0 +1,70 @@ +/* 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 "ASLayoutRangeType.h" +#import "ASViewController.h" +#import "ASRangeController.h" +#import "ASCollectionNode.h" +#import "ASTableNode.h" + + +@protocol ASRangeControllerUpdateRangeProtocol + +/** + * Updates the current range mode of the range controller for at least the next range update. + */ +- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; + +/** + * Only ASLayoutRangeModeVisibleOnly or ASLayoutRangeModeLowMemory are recommended. Default is ASLayoutRangeModeVisibleOnly, + * because this is the only way to ensure an application will not have blank / flashing views as the user navigates back after + * a memory warning. Apps that wish to use the more effective / aggressive ASLayoutRangeModeLowMemory may need to take steps + * to mitigate this behavior, including: restoring a larger range mode to the next controller before the user navigates there, + * enabling .neverShowPlaceholders on ASCellNodes so that the navigation operation is blocked on redisplay completing, etc. + */ ++ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode; + +@end + + +@interface ASRangeController (ASRangeControllerUpdateRangeProtocol) + +/** + * Update the range mode for a range controller to a explicitly set mode until the node that contains the range + * controller becomes visible again + * + * Logic for the automatic range mode: + * 1. If there are no visible node paths available nothing is to be done and no range update will happen + * 2. The initial range update if the range controller is visible always will be ASLayoutRangeModeCount + * (ASLayoutRangeModeMinimum) as it's the initial fetch + * 3. The range mode set explicitly via updateCurrentRangeWithMode: will last at least one range update. After that it + the range controller will use the explicit set range mode until it becomes visible and a new range update was + triggered or a new range mode via updateCurrentRangeWithMode: is set + * 4. If range mode is not explicitly set the range mode is variying based if the range controller is visible or not + */ +- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; + +@end + + +@interface ASCollectionNode (ASRangeControllerUpdateRangeProtocol) + +@end + + +@interface ASTableNode (ASRangeControllerUpdateRangeProtocol) + +@end + + +@interface ASViewController (ASRangeControllerUpdateRangeProtocol) + +/// Automatically adjust range mode based on view events if the containing node confirms to the ASRangeControllerUpdateRangeProtocol +@property (nonatomic, assign) BOOL automaticallyAdjustRangeModeBasedOnViewEvents; + +@end diff --git a/AsyncDisplayKit/Details/ASWeakProxy.h b/AsyncDisplayKit/Details/ASWeakProxy.h new file mode 100644 index 0000000000..d0598bb647 --- /dev/null +++ b/AsyncDisplayKit/Details/ASWeakProxy.h @@ -0,0 +1,17 @@ +// +// ASWeakProxy.h +// AsyncDisplayKit +// +// Created by Garrett Moon on 4/12/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface ASWeakProxy : NSObject + +@property (nonatomic, weak, readonly) id target; + ++ (instancetype)weakProxyWithTarget:(id)target; + +@end diff --git a/AsyncDisplayKit/Details/ASWeakProxy.m b/AsyncDisplayKit/Details/ASWeakProxy.m new file mode 100644 index 0000000000..e30f4e7e4f --- /dev/null +++ b/AsyncDisplayKit/Details/ASWeakProxy.m @@ -0,0 +1,31 @@ +// +// ASWeakProxy.m +// AsyncDisplayKit +// +// Created by Garrett Moon on 4/12/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASWeakProxy.h" + +@implementation ASWeakProxy + +- (instancetype)initWithTarget:(id)target +{ + if (self = [super init]) { + _target = target; + } + return self; +} + ++ (instancetype)weakProxyWithTarget:(id)target +{ + return [[ASWeakProxy alloc] initWithTarget:target]; +} + +- (id)forwardingTargetForSelector:(SEL)aSelector +{ + return _target; +} + +@end diff --git a/AsyncDisplayKit/Details/CGRect+ASConvenience.h b/AsyncDisplayKit/Details/CGRect+ASConvenience.h index 1d672b5f15..26ca6fd431 100644 --- a/AsyncDisplayKit/Details/CGRect+ASConvenience.h +++ b/AsyncDisplayKit/Details/CGRect+ASConvenience.h @@ -16,6 +16,18 @@ NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN +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 ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection, + ASRangeTuningParameters rangeTuningParameters); + CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect, ASRangeTuningParameters tuningParameters, ASScrollDirection scrollableDirections, diff --git a/AsyncDisplayKit/Details/CGRect+ASConvenience.m b/AsyncDisplayKit/Details/CGRect+ASConvenience.m index 312bac61f2..d3be682c78 100644 --- a/AsyncDisplayKit/Details/CGRect+ASConvenience.m +++ b/AsyncDisplayKit/Details/CGRect+ASConvenience.m @@ -10,12 +10,6 @@ #import "ASScrollDirection.h" #import "ASLayoutController.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) { diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h index 3670f2da16..e26d76b98a 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h @@ -54,8 +54,8 @@ extern NSInteger const ASDefaultTransactionPriority; @param callbackQueue The dispatch queue that the completion blocks will be called on. @param completionBlock A block that is called when the transaction is completed. May be NULL. */ -- (id)initWithCallbackQueue:(dispatch_queue_t)callbackQueue - completionBlock:(asyncdisplaykit_async_transaction_completion_block_t)completionBlock; +- (instancetype)initWithCallbackQueue:(dispatch_queue_t)callbackQueue + completionBlock:(asyncdisplaykit_async_transaction_completion_block_t)completionBlock; /** @summary Block the main thread until the transaction is complete, including callbacks. @@ -158,7 +158,6 @@ extern NSInteger const ASDefaultTransactionPriority; /** @summary Adds a block to run on the completion of the async transaction. - @param queue The dispatch queue on which to execute the block. @param completion The completion block that will be executed with the output of the execution block when all of the operations in the transaction are completed. Executed and released on callbackQueue. */ @@ -168,7 +167,7 @@ extern NSInteger const ASDefaultTransactionPriority; /** @summary Cancels all operations in the transaction. - @desc You can only cancel a commmitted transaction. + @desc You can only cancel a committed transaction. All completion blocks are always called, regardless of cancelation. Execution blocks may be skipped if canceled. */ diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.mm b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.mm index 66a4fb0df9..5869be4166 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.mm +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.mm @@ -16,14 +16,14 @@ NSInteger const ASDefaultTransactionPriority = 0; @interface ASDisplayNodeAsyncTransactionOperation : NSObject -- (id)initWithOperationCompletionBlock:(asyncdisplaykit_async_transaction_operation_completion_block_t)operationCompletionBlock; +- (instancetype)initWithOperationCompletionBlock:(asyncdisplaykit_async_transaction_operation_completion_block_t)operationCompletionBlock; @property (nonatomic, copy) asyncdisplaykit_async_transaction_operation_completion_block_t operationCompletionBlock; -@property (atomic, retain) id value; // set on bg queue by the operation block +@property (atomic, strong) id value; // set on bg queue by the operation block @end @implementation ASDisplayNodeAsyncTransactionOperation -- (id)initWithOperationCompletionBlock:(asyncdisplaykit_async_transaction_operation_completion_block_t)operationCompletionBlock +- (instancetype)initWithOperationCompletionBlock:(asyncdisplaykit_async_transaction_operation_completion_block_t)operationCompletionBlock { if ((self = [super init])) { _operationCompletionBlock = [operationCompletionBlock copy]; @@ -236,7 +236,7 @@ void ASAsyncTransactionQueue::GroupImpl::schedule(NSInteger priority, dispatch_q operation._block(); } operation._group->leave(); - operation._block = 0; // the block must be freed while mutex is unlocked + operation._block = nil; // the block must be freed while mutex is unlocked } } --entry._threadCount; @@ -315,8 +315,8 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance() #pragma mark - #pragma mark Lifecycle -- (id)initWithCallbackQueue:(dispatch_queue_t)callbackQueue - completionBlock:(void(^)(_ASAsyncTransaction *, BOOL))completionBlock +- (instancetype)initWithCallbackQueue:(dispatch_queue_t)callbackQueue + completionBlock:(void(^)(_ASAsyncTransaction *, BOOL))completionBlock { if ((self = [self init])) { if (callbackQueue == NULL) { @@ -484,7 +484,7 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance() ASDisplayNodeAssert(_state != ASAsyncTransactionStateOpen, @"Transaction should not be open after committing group"); } // If we needed to commit the group above, -completeTransaction may have already been run. - // It is designed to accomodate this by checking _state to ensure it is not complete. + // It is designed to accommodate this by checking _state to ensure it is not complete. [self completeTransaction]; } } diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer+Private.h b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer+Private.h index 32421c2543..690ce93cd6 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer+Private.h +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer+Private.h @@ -10,8 +10,8 @@ @interface CALayer (ASAsyncTransactionContainerTransactions) -@property (nonatomic, retain, setter=asyncdisplaykit_setAsyncLayerTransactions:) NSHashTable *asyncdisplaykit_asyncLayerTransactions; -@property (nonatomic, retain, setter=asyncdisplaykit_setCurrentAsyncLayerTransaction:) _ASAsyncTransaction *asyncdisplaykit_currentAsyncLayerTransaction; +@property (nonatomic, strong, setter=asyncdisplaykit_setAsyncLayerTransactions:) NSHashTable *asyncdisplaykit_asyncLayerTransactions; +@property (nonatomic, strong, setter=asyncdisplaykit_setCurrentAsyncLayerTransaction:) _ASAsyncTransaction *asyncdisplaykit_currentAsyncLayerTransaction; - (void)asyncdisplaykit_asyncTransactionContainerWillBeginTransaction:(_ASAsyncTransaction *)transaction; - (void)asyncdisplaykit_asyncTransactionContainerDidCompleteTransaction:(_ASAsyncTransaction *)transaction; diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.h b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.h index 628d461a39..9626418804 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.h +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.h @@ -59,13 +59,13 @@ typedef NS_ENUM(NSUInteger, ASAsyncTransactionContainerState) { did not already exist. This method will always return an open, uncommitted transaction. @desc asyncdisplaykit_isAsyncTransactionContainer does not need to be YES for this to return a transaction. */ -@property (nonatomic, readonly, retain) _ASAsyncTransaction *asyncdisplaykit_asyncTransaction; +@property (nonatomic, readonly, strong) _ASAsyncTransaction *asyncdisplaykit_asyncTransaction; /** @summary Goes up the superlayer chain until it finds the first layer with asyncdisplaykit_isAsyncTransactionContainer=YES (including the receiver) and returns it. Returns nil if no parent container is found. */ -@property (nonatomic, readonly, retain) CALayer *asyncdisplaykit_parentTransactionContainer; +@property (nonatomic, readonly, strong) CALayer *asyncdisplaykit_parentTransactionContainer; @end @interface UIView (ASDisplayNodeAsyncTransactionContainer) diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h index c7b3da3217..198cf3a7f6 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h @@ -18,7 +18,7 @@ + (void)commit; /// Add a transaction container to be committed. -/// @param containerLayer A layer containing a transaction to be commited. May or may not be a container layer. +/// @param containerLayer A layer containing a transaction to be committed. May or may not be a container layer. /// @see ASAsyncTransactionContainer - (void)addTransactionContainer:(CALayer *)containerLayer; @end diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m index afebd4e5cc..5749ab7591 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m @@ -62,7 +62,7 @@ static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observ CFRelease(observer); } -- (id)init +- (instancetype)init { if ((self = [super init])) { _containerLayers = [NSHashTable hashTableWithOptions:NSPointerFunctionsObjectPointerPersonality]; diff --git a/AsyncDisplayKit/Details/UIView+ASConvenience.h b/AsyncDisplayKit/Details/UIView+ASConvenience.h index d84f79923f..b10e8900b1 100644 --- a/AsyncDisplayKit/Details/UIView+ASConvenience.h +++ b/AsyncDisplayKit/Details/UIView+ASConvenience.h @@ -20,20 +20,20 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) CGPoint position; @property (nonatomic, assign) CGFloat zPosition; @property (nonatomic, assign) CGPoint anchorPoint; -@property (nullable, nonatomic, retain) id contents; +@property (nullable, nonatomic, strong) id contents; @property (nonatomic, assign) CGFloat cornerRadius; @property (nonatomic, assign) CGFloat contentsScale; @property (nonatomic, assign) CATransform3D transform; @property (nonatomic, assign) CATransform3D sublayerTransform; @property (nonatomic, assign) BOOL needsDisplayOnBoundsChange; -@property (nonatomic, retain) __attribute__((NSObject)) CGColorRef shadowColor; +@property (nonatomic, strong) __attribute__((NSObject)) CGColorRef shadowColor; @property (nonatomic, assign) CGFloat shadowOpacity; @property (nonatomic, assign) CGSize shadowOffset; @property (nonatomic, assign) CGFloat shadowRadius; @property (nonatomic, assign) CGFloat borderWidth; @property (nonatomic, assign, getter = isOpaque) BOOL opaque; -@property (nonatomic, retain) __attribute__((NSObject)) CGColorRef borderColor; -@property (nonatomic, retain) __attribute__((NSObject)) CGColorRef backgroundColor; +@property (nonatomic, strong) __attribute__((NSObject)) CGColorRef borderColor; +@property (nonatomic, strong) __attribute__((NSObject)) CGColorRef backgroundColor; @property (nonatomic, assign) BOOL allowsEdgeAntialiasing; @property (nonatomic, assign) unsigned int edgeAntialiasingMask; @@ -51,7 +51,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, getter=isHidden) BOOL hidden; @property (nonatomic, assign) BOOL autoresizesSubviews; @property (nonatomic, assign) UIViewAutoresizing autoresizingMask; -@property (nonatomic, retain, null_resettable) UIColor *tintColor; +@property (nonatomic, strong, null_resettable) UIColor *tintColor; @property (nonatomic, assign) CGFloat alpha; @property (nonatomic, assign) CGRect bounds; @property (nonatomic, assign) CGRect frame; // Only for use with nodes wrapping synchronous views @@ -71,7 +71,7 @@ NS_ASSUME_NONNULL_BEGIN @property (atomic, copy) NSString *accessibilityValue; @property (atomic, assign) UIAccessibilityTraits accessibilityTraits; @property (atomic, assign) CGRect accessibilityFrame; - @property (atomic, retain) NSString *accessibilityLanguage; + @property (atomic, strong) NSString *accessibilityLanguage; @property (atomic, assign) BOOL accessibilityElementsHidden; @property (atomic, assign) BOOL accessibilityViewIsModal; @property (atomic, assign) BOOL shouldGroupAccessibilityChildren; diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.h b/AsyncDisplayKit/Details/_ASDisplayLayer.h index 95e59660a9..2687de523c 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.h +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.h @@ -81,7 +81,7 @@ typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); /** @summary Delegate method to draw layer contents into a CGBitmapContext. The current UIGraphics context will be set to an appropriate context. @param parameters An object describing all of the properties you need to draw. Return this from -drawParametersForAsyncLayer: - @param isCancelled Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. + @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. @param isRasterizing YES if the layer is being rasterized into another layer, in which case drawRect: probably wants to avoid doing things like filling its bounds with a zero-alpha color to clear the backing store. */ + (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; @@ -89,7 +89,7 @@ typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); /** @summary Delegate override to provide new layer contents as a UIImage. @param parameters An object describing all of the properties you need to draw. Return this from -drawParametersForAsyncLayer: - @param isCancelled Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. + @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. @return A UIImage with contents that are ready to display on the main thread. Make sure that the image is already decoded before returning it here. */ + (UIImage *)displayWithParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock; diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index 110336be97..e7316e8bd0 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -31,7 +31,7 @@ #pragma mark - #pragma mark Lifecycle -- (id)init +- (instancetype)init { if ((self = [super init])) { _displaySentinel = [[ASSentinel alloc] init]; @@ -57,12 +57,6 @@ _asyncDelegate = asyncDelegate; } -- (void)setContents:(id)contents -{ - ASDisplayNodeAssertMainThread(); - [super setContents:contents]; -} - - (BOOL)isDisplaySuspended { ASDN::MutexLocker l(_displaySuspendedLock); @@ -84,6 +78,26 @@ } } +- (void)setBounds:(CGRect)bounds +{ + [super setBounds:bounds]; + self.asyncdisplaykit_node.threadSafeBounds = bounds; +} + +#if DEBUG // These override is strictly to help detect application-level threading errors. Avoid method overhead in release. +- (void)setContents:(id)contents +{ + ASDisplayNodeAssertMainThread(); + [super setContents:contents]; +} + +- (void)setNeedsLayout +{ + ASDisplayNodeAssertMainThread(); + [super setNeedsLayout]; +} +#endif + - (void)layoutSublayers { [super layoutSublayers]; @@ -99,17 +113,12 @@ } } -- (void)setNeedsLayout -{ - ASDisplayNodeAssertMainThread(); - [super setNeedsLayout]; -} - - (void)setNeedsDisplay { ASDisplayNodeAssertMainThread(); - ASDN::MutexLocker l(_displaySuspendedLock); + _displaySuspendedLock.lock(); + // FIXME: Reconsider whether we should cancel a display in progress. // We should definitely cancel a display that is scheduled, but unstarted display. [self cancelAsyncDisplay]; @@ -118,6 +127,7 @@ if (!_displaySuspended) { [super setNeedsDisplay]; } + _displaySuspendedLock.unlock(); } #pragma mark - @@ -178,18 +188,29 @@ - (void)display:(BOOL)asynchronously { - [self _performBlockWithAsyncDelegate:^(id<_ASDisplayLayerDelegate> asyncDelegate) { - [asyncDelegate displayAsyncLayer:self asynchronously:asynchronously]; - }]; + id<_ASDisplayLayerDelegate> __attribute__((objc_precise_lifetime)) strongAsyncDelegate; + { + _asyncDelegateLock.lock(); + strongAsyncDelegate = _asyncDelegate; + _asyncDelegateLock.unlock(); + } + + [strongAsyncDelegate displayAsyncLayer:self asynchronously:asynchronously]; } - (void)cancelAsyncDisplay { ASDisplayNodeAssertMainThread(); [_displaySentinel increment]; - [self _performBlockWithAsyncDelegate:^(id<_ASDisplayLayerDelegate> asyncDelegate) { - [asyncDelegate cancelDisplayAsyncLayer:self]; - }]; + + id<_ASDisplayLayerDelegate> __attribute__((objc_precise_lifetime)) strongAsyncDelegate; + { + _asyncDelegateLock.lock(); + strongAsyncDelegate = _asyncDelegate; + _asyncDelegateLock.unlock(); + } + + [strongAsyncDelegate cancelDisplayAsyncLayer:self]; } - (NSString *)description @@ -199,17 +220,4 @@ return [NSString stringWithFormat:@"<%@, layer = %@>", self.asyncdisplaykit_node, [super description]]; } -#pragma mark - -#pragma mark Helper Methods - -- (void)_performBlockWithAsyncDelegate:(void(^)(id<_ASDisplayLayerDelegate> asyncDelegate))block -{ - id<_ASDisplayLayerDelegate> __attribute__((objc_precise_lifetime)) strongAsyncDelegate; - { - ASDN::MutexLocker l(_asyncDelegateLock); - strongAsyncDelegate = _asyncDelegate; - } - block(strongAsyncDelegate); -} - @end diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 25f92dc332..5c96f43ff2 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -8,12 +8,7 @@ #import "_ASDisplayView.h" -#import - #import "_ASCoreAnimationExtras.h" -#import "_ASAsyncTransactionContainer.h" -#import "ASAssert.h" -#import "ASDisplayNodeExtras.h" #import "ASDisplayNodeInternal.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Subclasses.h" @@ -23,7 +18,7 @@ // Keep the node alive while its view is active. If you create a view, add its layer to a layer hierarchy, then release // the view, the layer retains the view to prevent a crash. This replicates this behaviour for the node abstraction. -@property (nonatomic, retain, readwrite) ASDisplayNode *keepalive_node; +@property (nonatomic, strong, readwrite) ASDisplayNode *keepalive_node; @end @implementation _ASDisplayView @@ -31,6 +26,7 @@ __unsafe_unretained ASDisplayNode *_node; // Though UIView has a .node property added via category, since we can add an ivar to a subclass, use that for performance. BOOL _inHitTest; BOOL _inPointInside; + NSArray *_accessibleElements; } @synthesize asyncdisplaykit_node = _node; @@ -41,7 +37,7 @@ } #pragma mark - NSObject Overrides -- (id)init +- (instancetype)init { return [self initWithFrame:CGRectZero]; } @@ -55,7 +51,7 @@ #pragma mark - UIView Overrides -- (id)initWithFrame:(CGRect)frame +- (instancetype)initWithFrame:(CGRect)frame { if (!(self = [super initWithFrame:frame])) return nil; @@ -90,6 +86,11 @@ self.keepalive_node = _node; } else if (currentSuperview && !newSuperview) { + // Clearing keepalive_node may cause deallocation of the node. In this case, __exitHierarchy may not have an opportunity (e.g. _node will be cleared + // by the time -didMoveToWindow occurs after this) to clear the Visible interfaceState, which we need to do before deallocation to meet an API guarantee. + if (_node.inHierarchy) { + [_node __exitHierarchy]; + } self.keepalive_node = nil; } @@ -201,6 +202,12 @@ self.layer.contentsGravity = (contentMode != UIViewContentModeRedraw) ? ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode) : kCAGravityResize; } +- (void)setBounds:(CGRect)bounds +{ + [super setBounds:bounds]; + _node.threadSafeBounds = bounds; +} + #pragma mark - Event Handling + UIResponder Overrides - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { diff --git a/Base/ASDisplayNodeExtraIvars.h b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h similarity index 81% rename from Base/ASDisplayNodeExtraIvars.h rename to AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h index c55fc47c65..2a4eca699d 100644 --- a/Base/ASDisplayNodeExtraIvars.h +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h @@ -6,5 +6,4 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -typedef struct _ASDisplayNodeExtraIvars { -} ASDisplayNodeExtraIvars; +#import diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm new file mode 100644 index 0000000000..b08296cee1 --- /dev/null +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm @@ -0,0 +1,126 @@ +/* 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 "_ASDisplayViewAccessiblity.h" +#import "_ASDisplayView.h" +#import "ASDisplayNodeExtras.h" +#import "ASDisplayNode+FrameworkPrivate.h" + +#pragma mark - UIAccessibilityElement + +@implementation UIAccessibilityElement (_ASDisplayView) + ++ (UIAccessibilityElement *)accessibilityElementWithContainer:(id)container node:(ASDisplayNode *)node +{ + UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container]; + accessibilityElement.accessibilityIdentifier = node.accessibilityIdentifier; + accessibilityElement.accessibilityLabel = node.accessibilityLabel; + accessibilityElement.accessibilityHint = node.accessibilityHint; + accessibilityElement.accessibilityValue = node.accessibilityValue; + accessibilityElement.accessibilityTraits = node.accessibilityTraits; + return accessibilityElement; +} + +@end + + +#pragma mark - _ASDisplayView / UIAccessibilityContainer + +static NSArray *ASCollectUIAccessibilityElementsForNode(ASDisplayNode *viewNode, ASDisplayNode *subnode, id container) { + NSMutableArray *accessibleElements = [NSMutableArray array]; + ASDisplayNodePerformBlockOnEveryNodeBFS(subnode, ^(ASDisplayNode * _Nonnull currentNode) { + // For every subnode that is layer backed or it's supernode has shouldRasterizeDescendants enabled + // we have to create a UIAccessibilityElement as no view for this node exists + if (currentNode != viewNode && currentNode.isAccessibilityElement) { + UIAccessibilityElement *accessibilityElement = [UIAccessibilityElement accessibilityElementWithContainer:container node:currentNode]; + // As the node hierarchy is flattened it's necessary to convert the frame for each subnode in the tree to the + // coordinate system of the supernode + CGRect frame = [viewNode convertRect:currentNode.bounds fromNode:currentNode]; + accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, container); + [accessibleElements addObject:accessibilityElement]; + } + }); + + return [accessibleElements copy]; +} + +@interface _ASDisplayView () { + NSArray *_accessibleElements; +} +@end + +@implementation _ASDisplayView (UIAccessibilityContainer) + +#pragma mark - UIAccessibility + +- (NSArray *)accessibleElements +{ + ASDisplayNode *viewNode = self.asyncdisplaykit_node; + if (viewNode == nil) { + return nil; + } + + // Handle rasterize case + if (viewNode.shouldRasterizeDescendants) { + _accessibleElements = ASCollectUIAccessibilityElementsForNode(viewNode, viewNode, self); + return _accessibleElements; + } + + // Handle not rasterize case + NSMutableArray *accessibleElements = [NSMutableArray array]; + + for (ASDisplayNode *subnode in viewNode.subnodes) { + if (subnode.isAccessibilityElement) { + // An accessiblityElement can either be a UIView or a UIAccessibilityElement + id accessiblityElement = nil; + if (subnode.isLayerBacked) { + // No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node + accessiblityElement = [UIAccessibilityElement accessibilityElementWithContainer:self node:subnode]; + } else { + accessiblityElement = subnode.view; + } + [accessiblityElement setAccessibilityFrame:UIAccessibilityConvertFrameToScreenCoordinates(subnode.frame, self)]; + [accessibleElements addObject:accessiblityElement]; + } else if (subnode.isLayerBacked) { + // Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement + [accessibleElements addObjectsFromArray:ASCollectUIAccessibilityElementsForNode(viewNode, subnode, self)]; + } else if ([subnode accessibilityElementCount] > 0) { + // Add UIAccessibilityContainer + [accessibleElements addObject:subnode.view]; + } + } + _accessibleElements = [accessibleElements copy]; + + return _accessibleElements; +} + +- (NSInteger)accessibilityElementCount +{ + return [self accessibleElements].count; +} + +- (id)accessibilityElementAtIndex:(NSInteger)index +{ + ASDisplayNodeAssertNotNil(_accessibleElements, @"At this point _accessibleElements should be created."); + if (_accessibleElements == nil) { + return nil; + } + + return _accessibleElements[index]; +} + +- (NSInteger)indexOfAccessibilityElement:(id)element +{ + if (_accessibleElements == nil) { + return NSNotFound; + } + + return [_accessibleElements indexOfObject:element]; +} + +@end diff --git a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h index f8413d275e..75a48443b7 100644 --- a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h @@ -8,7 +8,7 @@ * */ -#import +#import /** How the child is centered within the spec. */ typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecCenteringOptions) { @@ -25,19 +25,22 @@ typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecCenteringOptions) { /** How much space the spec will take up. */ typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecSizingOptions) { /** The spec will take up the maximum size possible */ - ASCenterLayoutSpecSizingOptionDefault, + ASCenterLayoutSpecSizingOptionDefault = ASRelativeLayoutSpecSizingOptionDefault, /** The spec will take up the minimum size possible along the X axis */ - ASCenterLayoutSpecSizingOptionMinimumX = 1 << 0, + ASCenterLayoutSpecSizingOptionMinimumX = ASRelativeLayoutSpecSizingOptionMinimumWidth, /** The spec will take up the minimum size possible along the Y axis */ - ASCenterLayoutSpecSizingOptionMinimumY = 1 << 1, + ASCenterLayoutSpecSizingOptionMinimumY = ASRelativeLayoutSpecSizingOptionMinimumHeight, /** Convenience option to take up the minimum size along both the X and Y axis */ - ASCenterLayoutSpecSizingOptionMinimumXY = ASCenterLayoutSpecSizingOptionMinimumX | ASCenterLayoutSpecSizingOptionMinimumY, + ASCenterLayoutSpecSizingOptionMinimumXY = ASRelativeLayoutSpecSizingOptionMinimumSize }; NS_ASSUME_NONNULL_BEGIN -/** Lays out a single layoutable child and position it so that it is centered into the layout bounds. */ -@interface ASCenterLayoutSpec : ASLayoutSpec +/** Lays out a single layoutable child and position it so that it is centered into the layout bounds. + * NOTE: ASRelativeLayoutSpec offers all of the capabilities of Center, and more. + * Check it out if you would like to be able to position the child at any corner or the middle of an edge. + */ +@interface ASCenterLayoutSpec : ASRelativeLayoutSpec @property (nonatomic, assign) ASCenterLayoutSpecCenteringOptions centeringOptions; @property (nonatomic, assign) ASCenterLayoutSpecSizingOptions sizingOptions; diff --git a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm index 5e81e1f3d2..147142db1c 100644 --- a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm @@ -23,13 +23,14 @@ sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions child:(id)child; { - if (!(self = [super init])) { + ASRelativeLayoutSpecPosition verticalPosition = [self verticalPositionFromCenteringOptions:centeringOptions]; + ASRelativeLayoutSpecPosition horizontalPosition = [self horizontalPositionFromCenteringOptions:centeringOptions]; + + if (!(self = [super initWithHorizontalPosition:horizontalPosition verticalPosition:verticalPosition sizingOption:sizingOptions child:child])) { return nil; } - ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); _centeringOptions = centeringOptions; _sizingOptions = sizingOptions; - [self setChild:child]; return self; } @@ -44,61 +45,36 @@ { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); _centeringOptions = centeringOptions; + + [self setHorizontalPosition:[self horizontalPositionFromCenteringOptions:centeringOptions]]; + [self setVerticalPosition:[self verticalPositionFromCenteringOptions:centeringOptions]]; } - (void)setSizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); _sizingOptions = sizingOptions; + [self setSizingOption:sizingOptions]; } -- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +- (ASRelativeLayoutSpecPosition)horizontalPositionFromCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions { - 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 = [self.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 layoutWithLayoutableObject:self size:size sublayouts:@[sublayout]]; + BOOL centerX = (centeringOptions & ASCenterLayoutSpecCenteringX) != 0; + if (centerX) { + return ASRelativeLayoutSpecPositionCenter; + } else { + return ASRelativeLayoutSpecPositionStart; + } } -- (void)setChildren:(NSArray *)children +- (ASRelativeLayoutSpecPosition)verticalPositionFromCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions { - ASDisplayNodeAssert(NO, @"not supported by this layout spec"); -} - -- (NSArray *)children -{ - ASDisplayNodeAssert(NO, @"not supported by this layout spec"); - return nil; + BOOL centerY = (centeringOptions & ASCenterLayoutSpecCenteringY) != 0; + if (centerY) { + return ASRelativeLayoutSpecPositionCenter; + } else { + return ASRelativeLayoutSpecPositionStart; + } } @end diff --git a/AsyncDisplayKit/Layout/ASDimension.h b/AsyncDisplayKit/Layout/ASDimension.h index c96b2155ae..8a09eb5f2e 100644 --- a/AsyncDisplayKit/Layout/ASDimension.h +++ b/AsyncDisplayKit/Layout/ASDimension.h @@ -8,6 +8,7 @@ * */ +#pragma once #import #import @@ -34,6 +35,8 @@ typedef struct { extern ASRelativeDimension const ASRelativeDimensionUnconstrained; +#define isValidForLayout(x) ((isnormal(x) || x == 0.0) && x >= 0.0 && x < (CGFLOAT_MAX / 2.0)) + ASDISPLAYNODE_EXTERN_C_BEGIN NS_ASSUME_NONNULL_BEGIN @@ -58,6 +61,9 @@ extern CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat extern ASSizeRange ASSizeRangeMake(CGSize min, CGSize max); +/** Creates an ASSizeRange with the provided size as both min and max */ +extern ASSizeRange ASSizeRangeMakeExactSize(CGSize size); + /** Clamps the provided CGSize between the [min, max] bounds of this ASSizeRange. */ extern CGSize ASSizeRangeClamp(ASSizeRange sizeRange, CGSize size); diff --git a/AsyncDisplayKit/Layout/ASDimension.mm b/AsyncDisplayKit/Layout/ASDimension.mm index a1e42c4b76..4c7fd0a42b 100644 --- a/AsyncDisplayKit/Layout/ASDimension.mm +++ b/AsyncDisplayKit/Layout/ASDimension.mm @@ -77,6 +77,11 @@ ASSizeRange ASSizeRangeMake(CGSize min, CGSize max) ASSizeRange sizeRange; sizeRange.min = min; sizeRange.max = max; return sizeRange; } +ASSizeRange ASSizeRangeMakeExactSize(CGSize size) +{ + return ASSizeRangeMake(size, size); +} + CGSize ASSizeRangeClamp(ASSizeRange sizeRange, CGSize size) { return CGSizeMake(MAX(sizeRange.min.width, MIN(sizeRange.max.width, size.width)), @@ -95,7 +100,7 @@ struct _Range { { CGFloat newMin = MAX(min, other.min); CGFloat newMax = MIN(max, other.max); - if (!(newMin > newMax)) { + if (newMin <= newMax) { return {newMin, newMax}; } else { // No intersection. If we're before the other range, return our max; otherwise our min. diff --git a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm index 960360f8c4..ce7e6b4ea9 100644 --- a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm @@ -88,6 +88,12 @@ static CGFloat centerInset(CGFloat outer, CGFloat inner) MAX(0, constrainedSize.max.height - insetsY), } }; + + if (self.child == nil) { + ASDisplayNodeAssert(NO, @"Inset spec measured without a child. The spec will do nothing."); + return [ASLayout layoutWithLayoutableObject:self size:CGSizeZero]; + } + ASLayout *sublayout = [self.child measureWithSizeRange:insetConstrainedSize]; const CGSize computedSize = ASSizeRangeClamp(constrainedSize, { diff --git a/AsyncDisplayKit/Layout/ASLayout.h b/AsyncDisplayKit/Layout/ASLayout.h index 073e2a6a11..af7b99ad23 100644 --- a/AsyncDisplayKit/Layout/ASLayout.h +++ b/AsyncDisplayKit/Layout/ASLayout.h @@ -8,6 +8,8 @@ * */ +#pragma once + #import #import #import @@ -60,7 +62,7 @@ extern BOOL CGPointIsNull(CGPoint point); * * @param size The size of this layout. * - * @param position The posiion of this layout within its parent (if available). + * @param position The position of this layout within its parent (if available). * * @param sublayouts Sublayouts belong to the new layout. */ diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/AsyncDisplayKit/Layout/ASLayout.mm index 0e15fcfc16..3d710316ca 100644 --- a/AsyncDisplayKit/Layout/ASLayout.mm +++ b/AsyncDisplayKit/Layout/ASLayout.mm @@ -39,7 +39,15 @@ extern BOOL CGPointIsNull(CGPoint point) ASLayout *l = [super new]; if (l) { l->_layoutableObject = layoutableObject; - l->_size = CGSizeMake(ASCeilPixelValue(size.width), ASCeilPixelValue(size.height)); + + if (!isValidForLayout(size.width) || !isValidForLayout(size.height)) { + ASDisplayNodeAssert(NO, @"layoutSize is invalid and unsafe to provide to Core Animation! Production will force to 0, 0. Size = %@, node = %@", NSStringFromCGSize(size), layoutableObject); + size = CGSizeZero; + } else { + size = CGSizeMake(ASCeilPixelValue(size.width), ASCeilPixelValue(size.height)); + } + l->_size = size; + if (CGPointIsNull(position) == NO) { l->_position = CGPointMake(ASCeilPixelValue(position.x), ASCeilPixelValue(position.y)); } else { @@ -110,7 +118,7 @@ extern BOOL CGPointIsNull(CGPoint point) for (ASLayout *sublayout in context.layout.sublayouts) { // Mark layout trees that have already been flattened for future identification of immediate sublayouts - BOOL flattened = context.flattened ?: context.layout.flattened; + BOOL flattened = context.flattened ? : context.layout.flattened; queue.push({sublayout, context.absolutePosition + sublayout.position, NO, flattened}); } } diff --git a/AsyncDisplayKit/Layout/ASLayoutOptions.h b/AsyncDisplayKit/Layout/ASLayoutOptions.h deleted file mode 100644 index 7ce0f404e2..0000000000 --- a/AsyncDisplayKit/Layout/ASLayoutOptions.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASLayoutable; - -/** - * A store for all of the options defined by ASLayoutSpec subclasses. All implementors of ASLayoutable own a - * ASLayoutOptions. When certain layoutSpecs need option values, they are read from this class. - * - * Unless you wish to create a custom layout spec, ASLayoutOptions can largerly be ignored. Instead you can access - * the layout option properties exposed in ASLayoutable directly, which will set the values in ASLayoutOptions - * behind the scenes. - */ -@interface ASLayoutOptions : NSObject - -/** - * Sets the class name for the ASLayoutOptions subclasses that will be created when a node or layoutSpec's options - * are first accessed. - * - * If you create a custom layoutSpec that includes new options, you will want to subclass ASLayoutOptions to add - * the new layout options for your layoutSpec(s). In order to make sure your subclass is created instead of an - * instance of ASLayoutOptions, call setDefaultLayoutOptionsClass: early in app launch (applicationDidFinishLaunching:) - * with your subclass's class. - * - * @param defaultLayoutOptionsClass The class of ASLayoutOptions that will be lazily created for a node or layout spec. - */ -+ (void)setDefaultLayoutOptionsClass:(Class)defaultLayoutOptionsClass; - -/** - * @return the Class of ASLayoutOptions that will be created for a node or layoutspec. Defaults to [ASLayoutOptions class]; - */ -+ (Class)defaultLayoutOptionsClass; - -#pragma mark - Subclasses should implement these! -/** - * Initializes a new ASLayoutOptions using the given layoutable to assign any intrinsic option values. - * This init function sets a sensible default value for each layout option. If you create a subclass of - * ASLayoutOptions, your subclass should do the same. - * - * @param layoutable The layoutable that will own these options. The layoutable will be used to set any intrinsic - * layoutOptions. For example, if the layoutable is an ASTextNode the ascender/descender values will get set. - * - * @return a new instance of ASLayoutOptions - */ -- (instancetype)initWithLayoutable:(id)layoutable; - -/** - * Copies the values of layoutOptions into self. This is useful when placing a layoutable inside of another. Consider - * an ASTextNode that you want to align to the baseline by putting it in an ASStackLayoutSpec. Before that, you want - * to inset the ASTextNode by placing it in an ASInsetLayoutSpec. An ASInsetLayoutSpec will not have any information - * about the ASTextNode's ascender/descender unless we copy over the layout options from ASTextNode to ASInsetLayoutSpec. - * This is done automatically and should not need to be called directly. It is listed here to make sure that any - * ASLayoutOptions subclass implements the method. - * - * @param layoutOptions The layoutOptions to copy from - */ -- (void)copyFromOptions:(ASLayoutOptions *)layoutOptions; - -#pragma mark - ASStackLayoutable - -@property (nonatomic, readwrite) CGFloat spacingBefore; -@property (nonatomic, readwrite) CGFloat spacingAfter; -@property (nonatomic, readwrite) BOOL flexGrow; -@property (nonatomic, readwrite) BOOL flexShrink; -@property (nonatomic, readwrite) ASRelativeDimension flexBasis; -@property (nonatomic, readwrite) ASStackLayoutAlignSelf alignSelf; -@property (nonatomic, readwrite) CGFloat ascender; -@property (nonatomic, readwrite) CGFloat descender; - -#pragma mark - ASStaticLayoutable - -@property (nonatomic, readwrite) ASRelativeSizeRange sizeRange; -@property (nonatomic, readwrite) CGPoint layoutPosition; - -@end - -NS_ASSUME_NONNULL_END - diff --git a/AsyncDisplayKit/Layout/ASLayoutOptions.mm b/AsyncDisplayKit/Layout/ASLayoutOptions.mm deleted file mode 100644 index ad503014bf..0000000000 --- a/AsyncDisplayKit/Layout/ASLayoutOptions.mm +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import "ASLayoutOptions.h" - -#import -#import -#import -#import "ASInternalHelpers.h" - -@interface ASLayoutOptions() -{ - ASDN::RecursiveMutex _propertyLock; -} -@end - -@implementation ASLayoutOptions - -@synthesize spacingBefore = _spacingBefore; -@synthesize spacingAfter = _spacingAfter; -@synthesize flexGrow = _flexGrow; -@synthesize flexShrink = _flexShrink; -@synthesize flexBasis = _flexBasis; -@synthesize alignSelf = _alignSelf; - -@synthesize ascender = _ascender; -@synthesize descender = _descender; - -@synthesize sizeRange = _sizeRange; -@synthesize layoutPosition = _layoutPosition; - -static Class gDefaultLayoutOptionsClass = nil; -+ (void)setDefaultLayoutOptionsClass:(Class)defaultLayoutOptionsClass -{ - gDefaultLayoutOptionsClass = defaultLayoutOptionsClass; -} - -+ (Class)defaultLayoutOptionsClass -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - if (gDefaultLayoutOptionsClass == nil) { - // If someone is asking for this and it hasn't been customized yet, use the default. - gDefaultLayoutOptionsClass = [ASLayoutOptions class]; - } - }); - return gDefaultLayoutOptionsClass; -} - -- (instancetype)init -{ - return nil; -} - -- (instancetype)initWithLayoutable:(id)layoutable -{ - self = [super init]; - if (self) { - - self.flexBasis = ASRelativeDimensionUnconstrained; - self.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(CGSizeZero), ASRelativeSizeMakeWithCGSize(CGSizeZero)); - self.layoutPosition = CGPointZero; - - // The following properties use a default value of 0 which we do not need to assign. - // self.spacingBefore = 0; - // self.spacingAfter = 0; - // self.flexGrow = NO; - // self.flexShrink = NO; - // self.alignSelf = ASStackLayoutAlignSelfAuto; - // self.ascender = 0; - // self.descender = 0; - - [self setValuesFromLayoutable:layoutable]; - } - return self; -} - -#pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone -{ - ASLayoutOptions *copy = [[[self class] alloc] init]; - [copy copyFromOptions:self]; - return copy; -} - -- (void)copyFromOptions:(ASLayoutOptions *)layoutOptions -{ - ASDN::MutexLocker l(_propertyLock); - self.flexBasis = layoutOptions.flexBasis; - self.spacingAfter = layoutOptions.spacingAfter; - self.spacingBefore = layoutOptions.spacingBefore; - self.flexGrow = layoutOptions.flexGrow; - self.flexShrink = layoutOptions.flexShrink; - self.alignSelf = layoutOptions.alignSelf; - - self.ascender = layoutOptions.ascender; - self.descender = layoutOptions.descender; - - self.sizeRange = layoutOptions.sizeRange; - self.layoutPosition = layoutOptions.layoutPosition; -} - -/** - * Given an id, set up layout options that are intrinsically defined by the layoutable. - * - * While this could be done in the layoutable object itself, moving the logic into the ASLayoutOptions class - * allows a custom spec to set up defaults without needing to alter the layoutable itself. For example, - * image you were creating a custom baseline spec that needed ascender/descender. To assign values automatically - * when a text node's attribute string is set, you would need to subclass ASTextNode and assign the values in the - * override of setAttributeString. However, assigning the defaults in an ASLayoutOptions subclass's - * setValuesFromLayoutable allows you to create a custom spec without the need to create a - * subclass of ASTextNode. - * - * @param layoutable The layoutable object to inspect for default intrinsic layout option values - */ -- (void)setValuesFromLayoutable:(id)layoutable -{ - ASDN::MutexLocker l(_propertyLock); - if ([layoutable isKindOfClass:[ASDisplayNode class]]) { - ASDisplayNode *displayNode = (ASDisplayNode *)layoutable; - self.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(displayNode.preferredFrameSize), ASRelativeSizeMakeWithCGSize(displayNode.preferredFrameSize)); - - if ([layoutable isKindOfClass:[ASTextNode class]]) { - ASTextNode *textNode = (ASTextNode *)layoutable; - NSAttributedString *attributedString = textNode.attributedString; - if (attributedString.length > 0) { - CGFloat screenScale = ASScreenScale(); - self.ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale; - self.descender = round([[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale; - } - } - - } -} - -- (CGFloat)spacingAfter -{ - ASDN::MutexLocker l(_propertyLock); - return _spacingAfter; -} - -- (void)setSpacingAfter:(CGFloat)spacingAfter -{ - ASDN::MutexLocker l(_propertyLock); - _spacingAfter = spacingAfter; -} - -- (CGFloat)spacingBefore -{ - ASDN::MutexLocker l(_propertyLock); - return _spacingBefore; -} - -- (void)setSpacingBefore:(CGFloat)spacingBefore -{ - ASDN::MutexLocker l(_propertyLock); - _spacingBefore = spacingBefore; -} - -- (BOOL)flexGrow -{ - ASDN::MutexLocker l(_propertyLock); - return _flexGrow; -} - -- (void)setFlexGrow:(BOOL)flexGrow -{ - ASDN::MutexLocker l(_propertyLock); - _flexGrow = flexGrow; -} - -- (BOOL)flexShrink -{ - ASDN::MutexLocker l(_propertyLock); - return _flexShrink; -} - -- (void)setFlexShrink:(BOOL)flexShrink -{ - ASDN::MutexLocker l(_propertyLock); - _flexShrink = flexShrink; -} - -- (ASRelativeDimension)flexBasis -{ - ASDN::MutexLocker l(_propertyLock); - return _flexBasis; -} - -- (void)setFlexBasis:(ASRelativeDimension)flexBasis -{ - ASDN::MutexLocker l(_propertyLock); - _flexBasis = flexBasis; -} - -- (ASStackLayoutAlignSelf)alignSelf -{ - ASDN::MutexLocker l(_propertyLock); - return _alignSelf; -} - -- (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf -{ - ASDN::MutexLocker l(_propertyLock); - _alignSelf = alignSelf; -} - -- (CGFloat)ascender -{ - ASDN::MutexLocker l(_propertyLock); - return _ascender; -} - -- (void)setAscender:(CGFloat)ascender -{ - ASDN::MutexLocker l(_propertyLock); - _ascender = ascender; -} - -- (CGFloat)descender -{ - ASDN::MutexLocker l(_propertyLock); - return _descender; -} - -- (void)setDescender:(CGFloat)descender -{ - ASDN::MutexLocker l(_propertyLock); - _descender = descender; -} - -- (ASRelativeSizeRange)sizeRange -{ - ASDN::MutexLocker l(_propertyLock); - return _sizeRange; -} - -- (void)setSizeRange:(ASRelativeSizeRange)sizeRange -{ - ASDN::MutexLocker l(_propertyLock); - _sizeRange = sizeRange; -} - -- (CGPoint)layoutPosition -{ - ASDN::MutexLocker l(_propertyLock); - return _layoutPosition; -} - -- (void)setLayoutPosition:(CGPoint)layoutPosition -{ - ASDN::MutexLocker l(_propertyLock); - _layoutPosition = layoutPosition; -} - -@end diff --git a/AsyncDisplayKit/Layout/ASLayoutOptionsPrivate.mm b/AsyncDisplayKit/Layout/ASLayoutOptionsPrivate.mm deleted file mode 100644 index 64b3f7b629..0000000000 --- a/AsyncDisplayKit/Layout/ASLayoutOptionsPrivate.mm +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import "ASLayoutOptionsPrivate.h" -#import -#import "ASThread.h" - - -/** - * Both an ASDisplayNode and an ASLayoutSpec conform to ASLayoutable. There are several properties - * in ASLayoutable that are used as layoutOptions when a node or spec is used in a layout spec. - * These properties are provided for convenience, as they are forwards to the node or spec's - * ASLayoutOptions class. Instead of duplicating the property forwarding in both classes, we - * create a define that allows us to easily implement the forwards in one place. - * - * If you create a custom layout spec, we recommend this stragety if you decide to extend - * ASDisplayNode and ASLAyoutSpec to provide convenience properties for any options that your - * layoutSpec may require. - */ -#define ASLayoutOptionsForwarding \ -- (ASLayoutOptions *)layoutOptions\ -{\ -ASDN::MutexLocker l(_layoutOptionsLock);\ -if (_layoutOptions == nil) {\ -_layoutOptions = [[[ASLayoutOptions defaultLayoutOptionsClass] alloc] initWithLayoutable:self];\ -}\ -return _layoutOptions;\ -}\ -\ -- (CGFloat)spacingBefore\ -{\ -return self.layoutOptions.spacingBefore;\ -}\ -\ -- (void)setSpacingBefore:(CGFloat)spacingBefore\ -{\ -self.layoutOptions.spacingBefore = spacingBefore;\ -}\ -\ -- (CGFloat)spacingAfter\ -{\ -return self.layoutOptions.spacingAfter;\ -}\ -\ -- (void)setSpacingAfter:(CGFloat)spacingAfter\ -{\ -self.layoutOptions.spacingAfter = spacingAfter;\ -}\ -\ -- (BOOL)flexGrow\ -{\ -return self.layoutOptions.flexGrow;\ -}\ -\ -- (void)setFlexGrow:(BOOL)flexGrow\ -{\ -self.layoutOptions.flexGrow = flexGrow;\ -}\ -\ -- (BOOL)flexShrink\ -{\ -return self.layoutOptions.flexShrink;\ -}\ -\ -- (void)setFlexShrink:(BOOL)flexShrink\ -{\ -self.layoutOptions.flexShrink = flexShrink;\ -}\ -\ -- (ASRelativeDimension)flexBasis\ -{\ -return self.layoutOptions.flexBasis;\ -}\ -\ -- (void)setFlexBasis:(ASRelativeDimension)flexBasis\ -{\ -self.layoutOptions.flexBasis = flexBasis;\ -}\ -\ -- (ASStackLayoutAlignSelf)alignSelf\ -{\ -return self.layoutOptions.alignSelf;\ -}\ -\ -- (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf\ -{\ - self.layoutOptions.alignSelf = alignSelf;\ -}\ -\ -- (CGFloat)ascender\ -{\ - return self.layoutOptions.ascender;\ -}\ -\ -- (void)setAscender:(CGFloat)ascender\ -{\ - self.layoutOptions.ascender = ascender;\ -}\ -\ -- (CGFloat)descender\ -{\ - return self.layoutOptions.descender;\ -}\ -\ -- (void)setDescender:(CGFloat)descender\ -{\ - self.layoutOptions.descender = descender;\ -}\ -\ -- (ASRelativeSizeRange)sizeRange\ -{\ - return self.layoutOptions.sizeRange;\ -}\ -\ -- (void)setSizeRange:(ASRelativeSizeRange)sizeRange\ -{\ - self.layoutOptions.sizeRange = sizeRange;\ -}\ -\ -- (CGPoint)layoutPosition\ -{\ - return self.layoutOptions.layoutPosition;\ -}\ -\ -- (void)setLayoutPosition:(CGPoint)position\ -{\ - self.layoutOptions.layoutPosition = position;\ -}\ - - -@implementation ASDisplayNode(ASLayoutOptions) -ASLayoutOptionsForwarding -@end - -@implementation ASLayoutSpec(ASLayoutOptions) -ASLayoutOptionsForwarding -@end diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.h b/AsyncDisplayKit/Layout/ASLayoutSpec.h index da6de78e0f..02a6d4f035 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.h @@ -25,13 +25,18 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init; +/** + * Parent of the layout spec + */ +@property (nullable, nonatomic, weak) id parent; + /** * Adds a child to this layout spec using a default identifier. * * @param child A child to be added. * * @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the - * reponsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec, + * responsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec, * only require a single child. * * For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example) @@ -50,7 +55,7 @@ NS_ASSUME_NONNULL_BEGIN * @param identifier An identifier associated with the child. * * @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the - * reponsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec, + * responsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec, * only require a single child. * * For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example) @@ -95,8 +100,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable id)childForIdentifier:(NSString *)identifier; -/** Returns all children added to this layout spec. */ -- (NSArray> *)children; +/** + * Returns all children added to this layout spec. + */ +- (nullable NSArray> *)children; @end diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.mm b/AsyncDisplayKit/Layout/ASLayoutSpec.mm index abb428ebd1..d5830d0c79 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.mm @@ -8,30 +8,33 @@ * */ -#import "ASLayoutOptionsPrivate.h" +#import "ASLayoutSpec.h" #import "ASAssert.h" #import "ASBaseDefines.h" +#import "ASEnvironmentInternal.h" #import "ASInternalHelpers.h" #import "ASLayout.h" -#import "ASLayoutOptions.h" #import "ASThread.h" #import +#import -static NSString * const kDefaultChildKey = @"kDefaultChildKey"; -static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey"; - -@interface ASLayoutSpec() -@property (nonatomic, strong) NSMutableDictionary *layoutChildren; +@interface ASLayoutSpec() { + ASEnvironmentState _environmentState; + ASDN::RecursiveMutex _propertyLock; + + id _child; + NSArray *_children; + NSMutableDictionary *_childrenWithIdentifier; +} @end @implementation ASLayoutSpec // these dynamic properties all defined in ASLayoutOptionsPrivate.m -@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition, layoutOptions; -@synthesize layoutChildren = _layoutChildren; +@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition; @synthesize isFinalLayoutable = _isFinalLayoutable; - (instancetype)init @@ -40,6 +43,8 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey"; return nil; } _isMutable = YES; + _environmentState = ASEnvironmentStateMakeDefault(); + return self; } @@ -75,59 +80,122 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey"; id finalLayoutable = [child finalLayoutable]; if (finalLayoutable != child) { - [finalLayoutable.layoutOptions copyFromOptions:child.layoutOptions]; + if (ASEnvironmentStatePropagationEnabled()) { + ASEnvironmentStatePropagateUp(finalLayoutable, child.environmentState.layoutOptionsState); + } else { + // If state propagation is not enabled the layout options state needs to be copied manually + ASEnvironmentState finalLayoutableEnvironmentState = finalLayoutable.environmentState; + finalLayoutableEnvironmentState.layoutOptionsState = child.environmentState.layoutOptionsState; + finalLayoutable.environmentState = finalLayoutableEnvironmentState; + } return finalLayoutable; } } return child; } -- (NSMutableDictionary *)layoutChildren +- (NSMutableDictionary *)childrenWithIdentifier { - if (!_layoutChildren) { - _layoutChildren = [NSMutableDictionary dictionary]; + if (!_childrenWithIdentifier) { + _childrenWithIdentifier = [NSMutableDictionary dictionary]; + } + return _childrenWithIdentifier; +} + +- (void)setParent:(id)parent +{ + // FIXME: Locking should be evaluated here. _parent is not widely used yet, though. + _parent = parent; + + if ([parent supportsUpwardPropagation]) { + ASEnvironmentStatePropagateUp(parent, self.environmentState.layoutOptionsState); } - return _layoutChildren; } - (void)setChild:(id)child; { - [self setChild:child forIdentifier:kDefaultChildKey]; + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + + id finalLayoutable = [self layoutableToAddFromLayoutable:child]; + _child = finalLayoutable; + [self propagateUpLayoutable:finalLayoutable]; } - (void)setChild:(id)child forIdentifier:(NSString *)identifier { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - self.layoutChildren[identifier] = [self layoutableToAddFromLayoutable:child];; + + id finalLayoutable = [self layoutableToAddFromLayoutable:child]; + self.childrenWithIdentifier[identifier] = finalLayoutable; + + // TODO: Should we propagate up the layoutable at it could happen that multiple children will propagated up their + // layout options and one child will overwrite values from another child + // [self propagateUpLayoutable:finalLayoutable]; } - (void)setChildren:(NSArray *)children { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - NSMutableArray *finalChildren = [NSMutableArray arrayWithCapacity:children.count]; + std::vector> finalChildren; for (id child in children) { - [finalChildren addObject:[self layoutableToAddFromLayoutable:child]]; + finalChildren.push_back([self layoutableToAddFromLayoutable:child]); } - self.layoutChildren[kDefaultChildrenKey] = [NSArray arrayWithArray:finalChildren]; + _children = nil; + if (finalChildren.size() > 0) { + _children = [NSArray arrayWithObjects:&finalChildren[0] count:finalChildren.size()]; + } } - (id)childForIdentifier:(NSString *)identifier { - return self.layoutChildren[identifier]; + return self.childrenWithIdentifier[identifier]; } - (id)child { - return self.layoutChildren[kDefaultChildKey]; + return _child; } - (NSArray *)children { - return self.layoutChildren[kDefaultChildrenKey]; + return [_children copy]; } + +#pragma mark - ASEnvironment + +- (ASEnvironmentState)environmentState +{ + return _environmentState; +} + +- (void)setEnvironmentState:(ASEnvironmentState)environmentState +{ + _environmentState = environmentState; +} + +// Subclasses can override this method to return NO, because upward propagation is not enabled if a layout +// specification has more than one child. Currently ASStackLayoutSpec and ASStaticLayoutSpec are currently +// the specifications that are known to have more than one. +- (BOOL)supportsUpwardPropagation +{ + return ASEnvironmentStatePropagationEnabled(); +} + +- (void)propagateUpLayoutable:(id)layoutable +{ + if ([layoutable isKindOfClass:[ASLayoutSpec class]]) { + [(ASLayoutSpec *)layoutable setParent:self]; // This will trigger upward propogation if needed. + } else if ([self supportsUpwardPropagation]) { + ASEnvironmentStatePropagateUp(self, layoutable.environmentState.layoutOptionsState); // Probably an ASDisplayNode + } +} + +ASEnvironmentLayoutOptionsForwarding +ASEnvironmentLayoutExtensibilityForwarding + @end @implementation ASLayoutSpec (Debugging) diff --git a/AsyncDisplayKit/Layout/ASLayoutable.h b/AsyncDisplayKit/Layout/ASLayoutable.h index 4653e38def..325a81535c 100644 --- a/AsyncDisplayKit/Layout/ASLayoutable.h +++ b/AsyncDisplayKit/Layout/ASLayoutable.h @@ -15,6 +15,8 @@ #import #import +#import +#import @class ASLayout; @class ASLayoutSpec; @@ -35,9 +37,9 @@ NS_ASSUME_NONNULL_BEGIN * These layout options are all stored in an ASLayoutOptions class (that is defined in ASLayoutablePrivate). * Generally you needn't worry about the layout options class, as the layoutable protocols allow all direct * access to the options via convenience properties. If you are creating custom layout spec, then you can - * extend the backing layout options class to accomodate any new layout options. + * extend the backing layout options class to accommodate any new layout options. */ -@protocol ASLayoutable +@protocol ASLayoutable /** * @abstract Calculate a layout based on given size range. diff --git a/AsyncDisplayKit/Layout/ASLayoutable.mm b/AsyncDisplayKit/Layout/ASLayoutable.mm new file mode 100644 index 0000000000..23fd59e9ff --- /dev/null +++ b/AsyncDisplayKit/Layout/ASLayoutable.mm @@ -0,0 +1,83 @@ +// +// ASLayoutablePrivate.mm +// AsyncDisplayKit +// +// Created by Huy Nguyen on 3/27/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASLayoutablePrivate.h" +#import "ASInternalHelpers.h" +#import "ASEnvironmentInternal.h" +#import "ASDisplayNodeInternal.h" +#import "ASTextNode.h" +#import "ASLayoutSpec.h" + +#import "pthread.h" +#import +#import +#import "ASThread.h" + +int32_t const ASLayoutableContextInvalidTransitionID = 0; +int32_t const ASLayoutableContextDefaultTransitionID = ASLayoutableContextInvalidTransitionID + 1; + +static inline ASLayoutableContext _ASLayoutableContextMake(int32_t transitionID, BOOL needsVisualizeNode) +{ + struct ASLayoutableContext context; + context.transitionID = transitionID; + context.needsVisualizeNode = needsVisualizeNode; + return context; +} + +static inline BOOL _IsValidTransitionID(int32_t transitionID) +{ + return transitionID > ASLayoutableContextInvalidTransitionID; +} + +struct ASLayoutableContext const ASLayoutableContextNull = _ASLayoutableContextMake(ASLayoutableContextInvalidTransitionID, NO); + +BOOL ASLayoutableContextIsNull(struct ASLayoutableContext context) +{ + return !_IsValidTransitionID(context.transitionID); +} + +ASLayoutableContext ASLayoutableContextMake(int32_t transitionID, BOOL needsVisualizeNode) +{ + NSCAssert(_IsValidTransitionID(transitionID), @"Invalid transition ID"); + return _ASLayoutableContextMake(transitionID, needsVisualizeNode); +} + +// Note: This is a non-recursive static lock. If it needs to be recursive, use ASDISPLAYNODE_MUTEX_RECURSIVE_INITIALIZER +static ASDN::StaticMutex _layoutableContextLock = ASDISPLAYNODE_MUTEX_INITIALIZER; +static std::map layoutableContextMap; + +static inline mach_port_t ASLayoutableGetCurrentContextKey() +{ + return pthread_mach_thread_np(pthread_self()); +} + +void ASLayoutableSetCurrentContext(struct ASLayoutableContext context) +{ + const mach_port_t key = ASLayoutableGetCurrentContextKey(); + ASDN::StaticMutexLocker l(_layoutableContextLock); + layoutableContextMap[key] = context; +} + +struct ASLayoutableContext ASLayoutableGetCurrentContext() +{ + const mach_port_t key = ASLayoutableGetCurrentContextKey(); + ASDN::StaticMutexLocker l(_layoutableContextLock); + const auto it = layoutableContextMap.find(key); + if (it != layoutableContextMap.end()) { + // Found an interator with above key. "it->first" is the key itself, "it->second" is the context value. + return it->second; + } + return ASLayoutableContextNull; +} + +void ASLayoutableClearCurrentContext() +{ + const mach_port_t key = ASLayoutableGetCurrentContextKey(); + ASDN::StaticMutexLocker l(_layoutableContextLock); + layoutableContextMap.erase(key); +} diff --git a/AsyncDisplayKit/Layout/ASLayoutableExtensibility.h b/AsyncDisplayKit/Layout/ASLayoutableExtensibility.h new file mode 100644 index 0000000000..62cadd3ee2 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASLayoutableExtensibility.h @@ -0,0 +1,25 @@ +// +// ASLayoutableExtensibility.h +// AsyncDisplayKit +// +// Created by Michael Schneider on 3/29/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@protocol ASLayoutableExtensibility + +// The maximum number of extended values per type are defined in ASEnvironment.h above the ASEnvironmentStateExtensions +// struct definition. If you try to set a value at an index after the maximum it will throw an assertion. + +- (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx; +- (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx; + +- (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx; +- (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx; + +- (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx; +- (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx; + +@end diff --git a/AsyncDisplayKit/Layout/ASLayoutablePrivate.h b/AsyncDisplayKit/Layout/ASLayoutablePrivate.h index f52dd54ad6..84ed0fe81d 100644 --- a/AsyncDisplayKit/Layout/ASLayoutablePrivate.h +++ b/AsyncDisplayKit/Layout/ASLayoutablePrivate.h @@ -11,9 +11,29 @@ #import @class ASLayoutSpec; -@class ASLayoutOptions; @protocol ASLayoutable; +struct ASLayoutableContext { + int32_t transitionID; + BOOL needsVisualizeNode; +}; + +extern int32_t const ASLayoutableContextInvalidTransitionID; + +extern int32_t const ASLayoutableContextDefaultTransitionID; + +extern struct ASLayoutableContext const ASLayoutableContextNull; + +extern BOOL ASLayoutableContextIsNull(struct ASLayoutableContext context); + +extern struct ASLayoutableContext ASLayoutableContextMake(int32_t transitionID, BOOL needsVisualizeNode); + +extern void ASLayoutableSetCurrentContext(struct ASLayoutableContext context); + +extern struct ASLayoutableContext ASLayoutableGetCurrentContext(); + +extern void ASLayoutableClearCurrentContext(); + /** * The base protocol for ASLayoutable. Generally the methods/properties in this class do not need to be * called by the end user and are only called internally. However, there may be a case where the methods are useful. @@ -39,9 +59,214 @@ */ @property (nonatomic, assign) BOOL isFinalLayoutable; +@end + + +#pragma mark - ASLayoutOptionsForwarding /** - * The class that holds all of the layoutOptions set on an ASLayoutable. + * Both an ASDisplayNode and an ASLayoutSpec conform to ASLayoutable. There are several properties + * in ASLayoutable that are used when a node or spec is used in a layout spec. + * These properties are provided for convenience, as they are forwards to the node or spec's + * properties. Instead of duplicating the property forwarding in both classes, we + * create a define that allows us to easily implement the forwards in one place. + * + * If you create a custom layout spec, we recommend this stragety if you decide to extend + * ASDisplayNode and ASLayoutSpec to provide convenience properties for any options that your + * layoutSpec may require. */ -@property (nonatomic, strong, readonly) ASLayoutOptions *layoutOptions; -@end + +#define ASEnvironmentLayoutOptionsForwarding \ +- (void)propagateUpLayoutOptionsState\ +{\ + if (!ASEnvironmentStatePropagationEnabled()) {\ + return;\ + }\ + id parent = [self parent];\ + if ([parent supportsUpwardPropagation]) {\ + ASEnvironmentStatePropagateUp(parent, _environmentState.layoutOptionsState);\ + }\ +}\ +\ +- (CGFloat)spacingAfter\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _environmentState.layoutOptionsState.spacingAfter;\ +}\ +\ +- (void)setSpacingAfter:(CGFloat)spacingAfter\ +{\ + _propertyLock.lock();\ + _environmentState.layoutOptionsState.spacingAfter = spacingAfter;\ + [self propagateUpLayoutOptionsState];\ + _propertyLock.unlock();\ +}\ +\ +- (CGFloat)spacingBefore\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _environmentState.layoutOptionsState.spacingBefore;\ +}\ +\ +- (void)setSpacingBefore:(CGFloat)spacingBefore\ +{\ + _propertyLock.lock();\ + _environmentState.layoutOptionsState.spacingBefore = spacingBefore;\ + [self propagateUpLayoutOptionsState];\ + _propertyLock.unlock();\ +}\ +\ +- (BOOL)flexGrow\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _environmentState.layoutOptionsState.flexGrow;\ +}\ +\ +- (void)setFlexGrow:(BOOL)flexGrow\ +{\ + _propertyLock.lock();\ + _environmentState.layoutOptionsState.flexGrow = flexGrow;\ + [self propagateUpLayoutOptionsState];\ + _propertyLock.unlock();\ +}\ +\ +- (BOOL)flexShrink\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _environmentState.layoutOptionsState.flexShrink;\ +}\ +\ +- (void)setFlexShrink:(BOOL)flexShrink\ +{\ + _propertyLock.lock();\ + _environmentState.layoutOptionsState.flexShrink = flexShrink;\ + [self propagateUpLayoutOptionsState];\ + _propertyLock.unlock();\ +}\ +\ +- (ASRelativeDimension)flexBasis\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _environmentState.layoutOptionsState.flexBasis;\ +}\ +\ +- (void)setFlexBasis:(ASRelativeDimension)flexBasis\ +{\ + _propertyLock.lock();\ + _environmentState.layoutOptionsState.flexBasis = flexBasis;\ + [self propagateUpLayoutOptionsState];\ + _propertyLock.unlock();\ +}\ +\ +- (ASStackLayoutAlignSelf)alignSelf\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _environmentState.layoutOptionsState.alignSelf;\ +}\ +\ +- (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf\ +{\ + _propertyLock.lock();\ + _environmentState.layoutOptionsState.alignSelf = alignSelf;\ + [self propagateUpLayoutOptionsState];\ + _propertyLock.unlock();\ +}\ +\ +- (CGFloat)ascender\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _environmentState.layoutOptionsState.ascender;\ +}\ +\ +- (void)setAscender:(CGFloat)ascender\ +{\ + _propertyLock.lock();\ + _environmentState.layoutOptionsState.ascender = ascender;\ + [self propagateUpLayoutOptionsState];\ + _propertyLock.unlock();\ +}\ +\ +- (CGFloat)descender\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _environmentState.layoutOptionsState.descender;\ +}\ +\ +- (void)setDescender:(CGFloat)descender\ +{\ + _propertyLock.lock();\ + _environmentState.layoutOptionsState.descender = descender;\ + [self propagateUpLayoutOptionsState];\ + _propertyLock.unlock();\ +}\ +\ +- (ASRelativeSizeRange)sizeRange\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _environmentState.layoutOptionsState.sizeRange;\ +}\ +\ +- (void)setSizeRange:(ASRelativeSizeRange)sizeRange\ +{\ + _propertyLock.lock();\ + _environmentState.layoutOptionsState.sizeRange = sizeRange;\ + [self propagateUpLayoutOptionsState];\ + _propertyLock.unlock();\ +}\ +\ +- (CGPoint)layoutPosition\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _environmentState.layoutOptionsState.layoutPosition;\ +}\ +\ +- (void)setLayoutPosition:(CGPoint)layoutPosition\ +{\ + _propertyLock.lock();\ + _environmentState.layoutOptionsState.layoutPosition = layoutPosition;\ + [self propagateUpLayoutOptionsState];\ + _propertyLock.unlock();\ +}\ + + +#pragma mark - ASLayoutableExtensibility + +#define ASEnvironmentLayoutExtensibilityForwarding \ +- (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx\ +{\ + _propertyLock.lock();\ + _ASEnvironmentLayoutOptionsExtensionSetBoolAtIndex(self, idx, value);\ + _propertyLock.unlock();\ +}\ +\ +- (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _ASEnvironmentLayoutOptionsExtensionGetBoolAtIndex(self, idx);\ +}\ +\ +- (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx\ +{\ + _propertyLock.lock();\ + _ASEnvironmentLayoutOptionsExtensionSetIntegerAtIndex(self, idx, value);\ + _propertyLock.unlock();\ +}\ +\ +- (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _ASEnvironmentLayoutOptionsExtensionGetIntegerAtIndex(self, idx);\ +}\ +\ +- (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx\ +{\ + _propertyLock.lock();\ + _ASEnvironmentLayoutOptionsExtensionSetEdgeInsetsAtIndex(self, idx, value);\ + _propertyLock.unlock();\ +}\ +\ +- (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx\ +{\ + ASDN::MutexLocker l(_propertyLock);\ + return _ASEnvironmentLayoutOptionsExtensionGetEdgeInsetsAtIndex(self, idx);\ +}\ diff --git a/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h b/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h new file mode 100644 index 0000000000..ddc88c0664 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h @@ -0,0 +1,73 @@ +// +// ASRelativeLayoutSpec.h +// AsyncDisplayKit +// +// Created by Samuel Stow on 12/31/15. +// + +#import + +/** How the child is positioned within the spec. */ +typedef NS_OPTIONS(NSUInteger, ASRelativeLayoutSpecPosition) { + /** The child is positioned at point 0 relatively to the layout axis (ie left / top most) */ + ASRelativeLayoutSpecPositionStart = 0, + /** The child is centered along the specified axis */ + ASRelativeLayoutSpecPositionCenter = 1 << 0, + /** The child is positioned at the maximum point of the layout axis (ie right / bottom most) */ + ASRelativeLayoutSpecPositionEnd = 1 << 1, +}; + +/** How much space the spec will take up. */ +typedef NS_OPTIONS(NSUInteger, ASRelativeLayoutSpecSizingOption) { + /** The spec will take up the maximum size possible */ + ASRelativeLayoutSpecSizingOptionDefault, + /** The spec will take up the minimum size possible along the X axis */ + ASRelativeLayoutSpecSizingOptionMinimumWidth = 1 << 0, + /** The spec will take up the minimum size possible along the Y axis */ + ASRelativeLayoutSpecSizingOptionMinimumHeight = 1 << 1, + /** Convenience option to take up the minimum size along both the X and Y axis */ + ASRelativeLayoutSpecSizingOptionMinimumSize = ASRelativeLayoutSpecSizingOptionMinimumWidth | ASRelativeLayoutSpecSizingOptionMinimumHeight, +}; + +NS_ASSUME_NONNULL_BEGIN + +/** Lays out a single layoutable child and positions it within the layout bounds according to vertical and horizontal positional specifiers. + * Can position the child at any of the 4 corners, or the middle of any of the 4 edges, as well as the center - similar to "9-part" image areas. + */ +@interface ASRelativeLayoutSpec : ASLayoutSpec + +// You may create a spec with alloc / init, then set any non-default properties; or use a convenience initialize that accepts all properties. +@property (nonatomic, assign) ASRelativeLayoutSpecPosition horizontalPosition; +@property (nonatomic, assign) ASRelativeLayoutSpecPosition verticalPosition; +@property (nonatomic, assign) ASRelativeLayoutSpecSizingOption sizingOption; + +/*! + * @discussion convenience constructor for a ASRelativeLayoutSpec + * @param horizontalPosition how to position the item on the horizontal (x) axis + * @param verticalPosition how to position the item on the vertical (y) axis + * @param sizingOption how much size to take up + * @param child the child to layout + * @return a configured ASRelativeLayoutSpec + */ ++ (instancetype)relativePositionLayoutSpecWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition + verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition + sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption + child:(id)child; + +/*! + * @discussion convenience initializer for a ASRelativeLayoutSpec + * @param horizontalPosition how to position the item on the horizontal (x) axis + * @param verticalPosition how to position the item on the vertical (y) axis + * @param sizingOption how much size to take up + * @param child the child to layout + * @return a configured ASRelativeLayoutSpec + */ +- (instancetype)initWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition + verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition + sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption + child:(id)child; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm b/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm new file mode 100644 index 0000000000..7a1a0ea5e0 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm @@ -0,0 +1,117 @@ +// +// ASRelativeLayoutSpec.mm +// AsyncDisplayKit +// +// Created by Samuel Stow on 12/31/15. +// + +#import "ASRelativeLayoutSpec.h" + +#import "ASInternalHelpers.h" +#import "ASLayout.h" + +@implementation ASRelativeLayoutSpec + +- (instancetype)initWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption child:(id)child +{ + if (!(self = [super init])) { + return nil; + } + ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); + _horizontalPosition = horizontalPosition; + _verticalPosition = verticalPosition; + _sizingOption = sizingOption; + [self setChild:child]; + return self; +} + ++ (instancetype)relativePositionLayoutSpecWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption child:(id)child +{ + return [[self alloc] initWithHorizontalPosition:horizontalPosition verticalPosition:verticalPosition sizingOption:sizingOption child:child]; +} + +- (void)setHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition +{ + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + _horizontalPosition = horizontalPosition; +} + +- (void)setVerticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition { + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + _verticalPosition = verticalPosition; +} + +- (void)setSizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption +{ + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + _sizingOption = sizingOption; +} + +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + CGSize size = { + constrainedSize.max.width, + constrainedSize.max.height + }; + + BOOL reduceWidth = (_horizontalPosition & ASRelativeLayoutSpecPositionCenter) != 0 || + (_horizontalPosition & ASRelativeLayoutSpecPositionEnd) != 0; + + BOOL reduceHeight = (_verticalPosition & ASRelativeLayoutSpecPositionCenter) != 0 || + (_verticalPosition & ASRelativeLayoutSpecPositionEnd) != 0; + + // Layout the child + const CGSize minChildSize = { + reduceWidth ? 0 : constrainedSize.min.width, + reduceHeight ? 0 : constrainedSize.min.height, + }; + ASLayout *sublayout = [self.child measureWithSizeRange:ASSizeRangeMake(minChildSize, constrainedSize.max)]; + + // If we have an undetermined height or width, use the child size to define the layout + // size + size = ASSizeRangeClamp(constrainedSize, { + isfinite(size.width) == NO ? sublayout.size.width : size.width, + isfinite(size.height) == NO ? 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, (_sizingOption & ASRelativeLayoutSpecSizingOptionMinimumWidth) != 0 ? sublayout.size.width : size.width), + MIN(size.height, (_sizingOption & ASRelativeLayoutSpecSizingOptionMinimumHeight) != 0 ? sublayout.size.height : size.height) + }); + + // Compute the position for the child on each axis according to layout parameters + CGFloat xPosition = [self proportionOfAxisForAxisPosition:_horizontalPosition]; + CGFloat yPosition = [self proportionOfAxisForAxisPosition:_verticalPosition]; + + sublayout.position = { + ASRoundPixelValue((size.width - sublayout.size.width) * xPosition), + ASRoundPixelValue((size.height - sublayout.size.height) * yPosition) + }; + + return [ASLayout layoutWithLayoutableObject:self size:size sublayouts:@[sublayout]]; +} + +- (void)setChildren:(NSArray *)children +{ + ASDisplayNodeAssert(NO, @"not supported by this layout spec"); +} + +- (NSArray *)children +{ + ASDisplayNodeAssert(NO, @"not supported by this layout spec"); + return nil; +} + +- (CGFloat)proportionOfAxisForAxisPosition:(ASRelativeLayoutSpecPosition)position +{ + if ((position & ASRelativeLayoutSpecPositionCenter) != 0) { + return 0.5f; + } else if ((position & ASRelativeLayoutSpecPositionEnd) != 0) { + return 1.0f; + } else { + return 0.0f; + } +} + +@end diff --git a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h b/AsyncDisplayKit/Layout/ASStackLayoutDefines.h index 2ed646ab67..48c0667a6b 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutDefines.h @@ -8,6 +8,8 @@ * */ +#import "ASBaseDefines.h" + /** The direction children are stacked in */ typedef NS_ENUM(NSUInteger, ASStackLayoutDirection) { /** Children are stacked vertically */ @@ -88,11 +90,19 @@ typedef NS_ENUM(NSUInteger, ASHorizontalAlignment) { /** No alignment specified. Default value */ ASHorizontalAlignmentNone, /** Left aligned */ - ASAlignmentLeft, + ASHorizontalAlignmentLeft, /** Center aligned */ - ASAlignmentMiddle, + ASHorizontalAlignmentMiddle, /** Right aligned */ - ASAlignmentRight, + ASHorizontalAlignmentRight, + + // After 2.0 has landed, we'll add ASDISPLAYNODE_DEPRECATED here - for now, avoid triggering errors for projects with -Werror + /** @deprecated Use ASHorizontalAlignmentLeft instead */ + ASAlignmentLeft = ASHorizontalAlignmentLeft, + /** @deprecated Use ASHorizontalAlignmentMiddle instead */ + ASAlignmentMiddle = ASHorizontalAlignmentMiddle, + /** @deprecated Use ASHorizontalAlignmentRight instead */ + ASAlignmentRight = ASHorizontalAlignmentRight, }; /** Orientation of children along vertical axis */ @@ -100,9 +110,17 @@ typedef NS_ENUM(NSUInteger, ASVerticalAlignment) { /** No alignment specified. Default value */ ASVerticalAlignmentNone, /** Top aligned */ - ASAlignmentTop, + ASVerticalAlignmentTop, /** Center aligned */ - ASAlignmentCenter, + ASVerticalAlignmentCenter, /** Bottom aligned */ - ASAlignmentBottom, + ASVerticalAlignmentBottom, + + // After 2.0 has landed, we'll add ASDISPLAYNODE_DEPRECATED here - for now, avoid triggering errors for projects with -Werror + /** @deprecated Use ASVerticalAlignmentTop instead */ + ASAlignmentTop = ASVerticalAlignmentTop, + /** @deprecated Use ASVerticalAlignmentCenter instead */ + ASAlignmentCenter = ASVerticalAlignmentCenter, + /** @deprecated Use ASVerticalAlignmentBottom instead */ + ASAlignmentBottom = ASVerticalAlignmentBottom, }; diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 76539bf57a..26b6d449c7 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -198,6 +198,15 @@ @end +@implementation ASStackLayoutSpec (ASEnvironment) + +- (BOOL)supportsUpwardPropagation +{ + return NO; +} + +@end + @implementation ASStackLayoutSpec (Debugging) #pragma mark - ASLayoutableAsciiArtProtocol diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm index 2d0edba4cd..a727376a57 100644 --- a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm @@ -11,7 +11,6 @@ #import "ASStaticLayoutSpec.h" #import "ASLayoutSpecUtilities.h" -#import "ASLayoutOptions.h" #import "ASInternalHelpers.h" #import "ASLayout.h" @@ -86,6 +85,15 @@ @end +@implementation ASStaticLayoutSpec (ASEnvironment) + +- (BOOL)supportsUpwardPropagation +{ + return NO; +} + +@end + @implementation ASStaticLayoutSpec (Debugging) #pragma mark - ASLayoutableAsciiArtProtocol diff --git a/AsyncDisplayKit/Private/ASBatchFetching.h b/AsyncDisplayKit/Private/ASBatchFetching.h index 9aeea5ad26..094a2c6c5d 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.h +++ b/AsyncDisplayKit/Private/ASBatchFetching.h @@ -10,10 +10,29 @@ #import "ASBatchContext.h" #import "ASScrollDirection.h" -#import "ASBaseDefines.h" ASDISPLAYNODE_EXTERN_C_BEGIN +@protocol ASBatchFetchingScrollView + +- (BOOL)canBatchFetch; +- (ASBatchContext *)batchContext; +- (CGFloat)leadingScreensForBatching; + +@end + +/** + @abstract Determine if batch fetching should begin based on the state of the parameters. + @discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and + * ASCollectionView batch fetching API. + @param context The scroll view that in-flight fetches are happening. + @param scrollDirection The current scrolling direction of the scroll view. + @param targetOffset The offset that the scrollview will scroll to. + @return Whether or not the current state should proceed with batch fetching. + */ +BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset); + + /** @abstract Determine if batch fetching should begin based on the state of the parameters. @param context The batch fetching context that contains knowledge about in-flight fetches. diff --git a/AsyncDisplayKit/Private/ASBatchFetching.m b/AsyncDisplayKit/Private/ASBatchFetching.m index c4027a70c5..6a8d9fc10d 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.m +++ b/AsyncDisplayKit/Private/ASBatchFetching.m @@ -8,35 +8,50 @@ #import "ASBatchFetching.h" +BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset) +{ + // Don't fetch if the scroll view does not allow + if (![scrollView canBatchFetch]) { + return NO; + } + + // Check if we should batch fetch + ASBatchContext *context = scrollView.batchContext; + CGRect bounds = scrollView.bounds; + CGSize contentSize = scrollView.contentSize; + CGFloat leadingScreens = scrollView.leadingScreensForBatching; + return ASDisplayShouldFetchBatchForContext(context, scrollDirection, bounds, contentSize, contentOffset, leadingScreens); +} + BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, - ASScrollDirection scrollDirection, - CGRect bounds, - CGSize contentSize, - CGPoint targetOffset, - CGFloat leadingScreens) { - // do not allow fetching if a batch is already in-flight and hasn't been completed or cancelled + ASScrollDirection scrollDirection, + CGRect bounds, + CGSize contentSize, + CGPoint targetOffset, + CGFloat leadingScreens) +{ + // Do not allow fetching if a batch is already in-flight and hasn't been completed or cancelled if ([context isFetching]) { return NO; } - // only Up and Left scrolls are currently supported (tail loading) - if (scrollDirection != ASScrollDirectionUp && scrollDirection != ASScrollDirectionLeft) { + // Only Down and Right scrolls are currently supported (tail loading) + if (!ASScrollDirectionContainsDown(scrollDirection) && !ASScrollDirectionContainsRight(scrollDirection)) { return NO; } - // no fetching for null states - if (leadingScreens <= 0.0 || - CGRectEqualToRect(bounds, CGRectZero)) { + // No fetching for null states + if (leadingScreens <= 0.0 || CGRectEqualToRect(bounds, CGRectZero)) { return NO; } CGFloat viewLength, offset, contentLength; - if (scrollDirection == ASScrollDirectionUp) { + if (ASScrollDirectionContainsDown(scrollDirection)) { viewLength = bounds.size.height; offset = targetOffset.y; contentLength = contentSize.height; - } else { // horizontal + } else { // horizontal / right viewLength = bounds.size.width; offset = targetOffset.x; contentLength = contentSize.width; diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index aa0982b77a..862497b86a 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -64,7 +64,7 @@ static void __ASDisplayLayerIncrementConcurrentDisplayCount(BOOL displayIsAsync, */ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, BOOL isRasterizing) { - // Displays while rasterizing are not counted as concurrent displays, becuase they draw in serial when their rasterizing container displays. + // Displays while rasterizing are not counted as concurrent displays, because they draw in serial when their rasterizing container displays. if (isRasterizing) { return; } @@ -92,7 +92,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, BOOL rasterizingFromAscendent = (_hierarchyState & ASHierarchyStateRasterized); - // if super node is rasterizing descendents, subnodes will not have had layout calls because they don't have layers + // if super node is rasterizing descendants, subnodes will not have had layout calls because they don't have layers if (rasterizingFromAscendent) { [self __layout]; } @@ -331,7 +331,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, // FIXME: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing // from the displayQueue? Need to not cancel early fails from displaySentinel changes. ASSentinel *displaySentinel = (asynchronously ? _displaySentinel : nil); - int64_t displaySentinelValue = [displaySentinel increment]; + int32_t displaySentinelValue = [displaySentinel increment]; asdisplaynode_iscancelled_block_t isCancelledBlock = ^{ return BOOL(displaySentinelValue != displaySentinel.value); @@ -370,7 +370,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, // while synchronizing the final application of the results to the layer's contents property (completionBlock). // First, look to see if we are expected to join a parent's transaction container. - CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ?: _layer; + CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ? : _layer; // In the case that a transaction does not yet exist (such as for an individual node outside of a container), // this call will allocate the transaction and add it to _ASAsyncTransactionGroup. diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index 4fdaa8632a..565593d37f 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -12,17 +12,15 @@ // #import "_AS-objc-internal.h" -#import "ASDisplayNodeExtraIvars.h" #import "ASDisplayNode.h" #import "ASSentinel.h" #import "ASThread.h" -#import "ASLayoutOptions.h" #import "_ASDisplayLayer.h" NS_ASSUME_NONNULL_BEGIN /** - Hierarchy state is propogated from nodes to all of their children when certain behaviors are required from the subtree. + Hierarchy state is propagated from nodes to all of their children when certain behaviors are required from the subtree. Examples include rasterization and external driving of the .interfaceState property. By passing this information explicitly, performance is optimized by avoiding iteration up the supernode chain. Lastly, this avoidance of supernode traversal protects against the possibility of deadlocks when a supernode is @@ -41,13 +39,26 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState) ASHierarchyStateRasterized = 1 << 0, /** The node or one of its supernodes is managed by a class like ASRangeController. Most commonly, these nodes are ASCellNode objects or a subnode of one, and are used in ASTableView or ASCollectionView. - These nodes also recieve regular updates to the .interfaceState property with more detailed status information. */ + These nodes also receive regular updates to the .interfaceState property with more detailed status information. */ ASHierarchyStateRangeManaged = 1 << 1, - /** Down-propogated version of _flags.visibilityNotificationsDisabled. This flag is very rarely set, but by having it + /** Down-propagated version of _flags.visibilityNotificationsDisabled. This flag is very rarely set, but by having it locally available to nodes, they do not have to walk up supernodes at the critical points it is checked. */ - ASHierarchyStateTransitioningSupernodes = 1 << 2 + ASHierarchyStateTransitioningSupernodes = 1 << 2, + /** One of the supernodes of this node is performing a transition. + Any layout calculated during this state should not be applied immediately, but pending until later. */ + ASHierarchyStateLayoutPending = 1 << 3 }; +inline BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState hierarchyState) +{ + return ((hierarchyState & ASHierarchyStateLayoutPending) == ASHierarchyStateLayoutPending); +} + +inline BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState) +{ + return ((hierarchyState & ASHierarchyStateRangeManaged) == ASHierarchyStateRangeManaged); +} + @interface ASDisplayNode () { @protected @@ -83,11 +94,19 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState) */ @property (nonatomic, readwrite) ASHierarchyState hierarchyState; +/** + * @abstract Return if the node is range managed or not + * + * @discussion Currently only set interface state on nodes in table and collection views. For other nodes, if they are + * in the hierarchy we enable all ASInterfaceState types with `ASInterfaceStateInHierarchy`, otherwise `None`. + */ +- (BOOL)supportsRangeManagedInterfaceState; + // The two methods below will eventually be exposed, but their names are subject to change. /** - * @abstract Ensure that all rendering is complete for this node and its descendents. + * @abstract Ensure that all rendering is complete for this node and its descendants. * - * @discussion Calling this method on the main thread after a node is added to the view heirarchy will ensure that + * @discussion Calling this method on the main thread after a node is added to the view hierarchy will ensure that * placeholder states are never visible to the user. It is used by ASTableView, ASCollectionView, and ASViewController * to implement their respective ".neverShowPlaceholders" option. * diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 923e9763ee..b84c308d7e 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -18,6 +18,7 @@ #import "ASEqualityHelpers.h" #import "ASPendingStateController.h" #import "ASThread.h" +#import "ASTextNode.h" /** * The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer. @@ -211,6 +212,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo { _bridge_prologue_write; _setToViewOrLayer(bounds, newBounds, bounds, newBounds); + self.threadSafeBounds = newBounds; } - (CGRect)frame @@ -300,7 +302,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo if (_hierarchyState & ASHierarchyStateRasterized) { ASPerformBlockOnMainThread(^{ // The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node - // begins materializing the view / layer heirarchy (locking itself or a descendant) while this node walks up + // begins materializing the view / layer hierarchy (locking itself or a descendant) while this node walks up // the tree and requires locking that node to access .shouldRasterizeDescendants. // For this reason, this method should be avoided when possible. Use _hierarchyState & ASHierarchyStateRasterized. ASDisplayNodeAssertMainThread(); @@ -358,10 +360,21 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo - (void)setOpaque:(BOOL)newOpaque { _bridge_prologue_write; - _setToLayer(opaque, newOpaque); - // NOTE: If we're in the background, then when the pending state - // is applied to the view on main, we will call `setNeedsDisplay` if - // the new opaque value doesn't match the one on the layer. + + BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + + if (shouldApply) { + BOOL oldOpaque = _layer.opaque; + _layer.opaque = newOpaque; + if (oldOpaque != newOpaque) { + [self setNeedsDisplay]; + } + } else { + // NOTE: If we're in the background, we cannot read the current value of self.opaque (if loaded). + // When the pending state is applied to the view on main, we will call `setNeedsDisplay` if + // the new opaque value doesn't match the one on the layer. + ASDisplayNodeGetPendingState(self).opaque = newOpaque; + } } - (BOOL)isUserInteractionEnabled @@ -563,10 +576,22 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo - (void)setBackgroundColor:(UIColor *)newBackgroundColor { _bridge_prologue_write; - _setToLayer(backgroundColor, newBackgroundColor.CGColor); - // NOTE: If we're in the background, then when the pending state - // is applied to the view on main, we will call `setNeedsDisplay` if - // the new background color doesn't match the one on the layer. + + CGColorRef newBackgroundCGColor = [newBackgroundColor CGColor]; + BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + + if (shouldApply) { + CGColorRef oldBackgroundCGColor = _layer.backgroundColor; + _layer.backgroundColor = newBackgroundCGColor; + if (!CGColorEqualToColor(oldBackgroundCGColor, newBackgroundCGColor)) { + [self setNeedsDisplay]; + } + } else { + // NOTE: If we're in the background, we cannot read the current value of bgcolor (if loaded). + // When the pending state is applied to the view on main, we will call `setNeedsDisplay` if + // the new background color doesn't match the one on the layer. + ASDisplayNodeGetPendingState(self).backgroundColor = newBackgroundCGColor; + } } - (UIColor *)tintColor @@ -684,141 +709,223 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo _setToLayer(edgeAntialiasingMask, edgeAntialiasingMask); } +@end + + +#pragma mark - UIViewBridgeAccessibility + +// ASDK supports accessibility for view or layer backed nodes. To be able to provide support for layer backed +// nodes, properties for all of the UIAccessibility protocol defined properties need to be provided an held in sync +// between node and view + +// Helper function with following logic: +// - If the node is not loaded yet use the property from the pending state +// - In case the node is loaded +// - Check if the node has a view and get the value from the view if loaded or from the pending state +// - If view is not available, e.g. the node is layer backed return the property value +#define _getAccessibilityFromViewOrProperty(nodeProperty, viewAndPendingViewStateProperty) __loaded(self) ? \ +(_view ? _view.viewAndPendingViewStateProperty : nodeProperty )\ +: ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty + +// Helper function to set property values on pending state or view and property if loaded +#define _setAccessibilityToViewAndProperty(nodeProperty, nodeValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) \ +nodeProperty = nodeValueExpr; _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) + +@implementation ASDisplayNode (UIViewBridgeAccessibility) + - (BOOL)isAccessibilityElement { _bridge_prologue_read; - return _getFromViewOnly(isAccessibilityElement); + return _getAccessibilityFromViewOrProperty(_isAccessibilityElement, isAccessibilityElement); } - (void)setIsAccessibilityElement:(BOOL)isAccessibilityElement { _bridge_prologue_write; - _setToViewOnly(isAccessibilityElement, isAccessibilityElement); + _setAccessibilityToViewAndProperty(_isAccessibilityElement, isAccessibilityElement, isAccessibilityElement, isAccessibilityElement); } - (NSString *)accessibilityLabel { _bridge_prologue_read; - return _getFromViewOnly(accessibilityLabel); + return _getAccessibilityFromViewOrProperty(_accessibilityLabel, accessibilityLabel); } - (void)setAccessibilityLabel:(NSString *)accessibilityLabel { _bridge_prologue_write; - _setToViewOnly(accessibilityLabel, accessibilityLabel); + _setAccessibilityToViewAndProperty(_accessibilityLabel, accessibilityLabel, accessibilityLabel, accessibilityLabel); } - (NSString *)accessibilityHint { _bridge_prologue_read; - return _getFromViewOnly(accessibilityHint); + return _getAccessibilityFromViewOrProperty(_accessibilityHint, accessibilityHint); } - (void)setAccessibilityHint:(NSString *)accessibilityHint { _bridge_prologue_write; - _setToViewOnly(accessibilityHint, accessibilityHint); + _setAccessibilityToViewAndProperty(_accessibilityHint, accessibilityHint, accessibilityHint, accessibilityHint); } - (NSString *)accessibilityValue { _bridge_prologue_read; - return _getFromViewOnly(accessibilityValue); + return _getAccessibilityFromViewOrProperty(_accessibilityValue, accessibilityValue); } - (void)setAccessibilityValue:(NSString *)accessibilityValue { _bridge_prologue_write; - _setToViewOnly(accessibilityValue, accessibilityValue); + _setAccessibilityToViewAndProperty(_accessibilityValue, accessibilityValue, accessibilityValue, accessibilityValue); } - (UIAccessibilityTraits)accessibilityTraits { _bridge_prologue_read; - return _getFromViewOnly(accessibilityTraits); + return _getAccessibilityFromViewOrProperty(_accessibilityTraits, accessibilityTraits); } - (void)setAccessibilityTraits:(UIAccessibilityTraits)accessibilityTraits { _bridge_prologue_write; - _setToViewOnly(accessibilityTraits, accessibilityTraits); + _setAccessibilityToViewAndProperty(_accessibilityTraits, accessibilityTraits, accessibilityTraits, accessibilityTraits); } - (CGRect)accessibilityFrame { _bridge_prologue_read; - return _getFromViewOnly(accessibilityFrame); + return _getAccessibilityFromViewOrProperty(_accessibilityFrame, accessibilityFrame); } - (void)setAccessibilityFrame:(CGRect)accessibilityFrame { _bridge_prologue_write; - _setToViewOnly(accessibilityFrame, accessibilityFrame); + _setAccessibilityToViewAndProperty(_accessibilityFrame, accessibilityFrame, accessibilityFrame, accessibilityFrame); } - (NSString *)accessibilityLanguage { _bridge_prologue_read; - return _getFromViewOnly(accessibilityLanguage); + return _getAccessibilityFromViewOrProperty(_accessibilityLanguage, accessibilityLanguage); } - (void)setAccessibilityLanguage:(NSString *)accessibilityLanguage { _bridge_prologue_write; - _setToViewOnly(accessibilityLanguage, accessibilityLanguage); + _setAccessibilityToViewAndProperty(_accessibilityLanguage, accessibilityLanguage, accessibilityLanguage, accessibilityLanguage); } - (BOOL)accessibilityElementsHidden { _bridge_prologue_read; - return _getFromViewOnly(accessibilityElementsHidden); + return _getAccessibilityFromViewOrProperty(_accessibilityElementsHidden, accessibilityElementsHidden); } - (void)setAccessibilityElementsHidden:(BOOL)accessibilityElementsHidden { _bridge_prologue_write; - _setToViewOnly(accessibilityElementsHidden, accessibilityElementsHidden); + _setAccessibilityToViewAndProperty(_accessibilityElementsHidden, accessibilityElementsHidden, accessibilityElementsHidden, accessibilityElementsHidden); } - (BOOL)accessibilityViewIsModal { _bridge_prologue_read; - return _getFromViewOnly(accessibilityViewIsModal); + return _getAccessibilityFromViewOrProperty(_accessibilityViewIsModal, accessibilityViewIsModal); } - (void)setAccessibilityViewIsModal:(BOOL)accessibilityViewIsModal { _bridge_prologue_write; - _setToViewOnly(accessibilityViewIsModal, accessibilityViewIsModal); + _setAccessibilityToViewAndProperty(_accessibilityViewIsModal, accessibilityViewIsModal, accessibilityViewIsModal, accessibilityViewIsModal); } - (BOOL)shouldGroupAccessibilityChildren { _bridge_prologue_read; - return _getFromViewOnly(shouldGroupAccessibilityChildren); + return _getAccessibilityFromViewOrProperty(_shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren); } - (void)setShouldGroupAccessibilityChildren:(BOOL)shouldGroupAccessibilityChildren { _bridge_prologue_write; - _setToViewOnly(shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren); + _setAccessibilityToViewAndProperty(_shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren); } - (NSString *)accessibilityIdentifier { _bridge_prologue_read; - return _getFromViewOnly(accessibilityIdentifier); + return _getAccessibilityFromViewOrProperty(_accessibilityIdentifier, accessibilityIdentifier); } - (void)setAccessibilityIdentifier:(NSString *)accessibilityIdentifier { _bridge_prologue_write; - _setToViewOnly(accessibilityIdentifier, accessibilityIdentifier); + _setAccessibilityToViewAndProperty(_accessibilityIdentifier, accessibilityIdentifier, accessibilityIdentifier, accessibilityIdentifier); +} + +- (void)setAccessibilityNavigationStyle:(UIAccessibilityNavigationStyle)accessibilityNavigationStyle +{ + _bridge_prologue_write; + _setAccessibilityToViewAndProperty(_accessibilityNavigationStyle, accessibilityNavigationStyle, accessibilityNavigationStyle, accessibilityNavigationStyle); +} + +- (UIAccessibilityNavigationStyle)accessibilityNavigationStyle +{ + _bridge_prologue_read; + return _getAccessibilityFromViewOrProperty(_accessibilityNavigationStyle, accessibilityNavigationStyle); +} + +#if TARGET_OS_TV +- (void)setAccessibilityHeaderElements:(NSArray *)accessibilityHeaderElements +{ + _bridge_prologue_write; + _setAccessibilityToViewAndProperty(_accessibilityHeaderElements, accessibilityHeaderElements, accessibilityHeaderElements, accessibilityHeaderElements); +} + +- (NSArray *)accessibilityHeaderElements +{ + _bridge_prologue_read; + return _getAccessibilityFromViewOrProperty(_accessibilityHeaderElements, accessibilityHeaderElements); +} +#endif + +- (void)setAccessibilityActivationPoint:(CGPoint)accessibilityActivationPoint +{ + _bridge_prologue_write; + _setAccessibilityToViewAndProperty(_accessibilityActivationPoint, accessibilityActivationPoint, accessibilityActivationPoint, accessibilityActivationPoint); +} + +- (CGPoint)accessibilityActivationPoint +{ + _bridge_prologue_read; + return _getAccessibilityFromViewOrProperty(_accessibilityActivationPoint, accessibilityActivationPoint); +} + +- (void)setAccessibilityPath:(UIBezierPath *)accessibilityPath +{ + _bridge_prologue_write; + _setAccessibilityToViewAndProperty(_accessibilityPath, accessibilityPath, accessibilityPath, accessibilityPath); +} + +- (UIBezierPath *)accessibilityPath +{ + _bridge_prologue_read; + return _getAccessibilityFromViewOrProperty(_accessibilityPath, accessibilityPath); +} + +- (NSInteger)accessibilityElementCount +{ + _bridge_prologue_read; + return _getFromViewOnly(accessibilityElementCount); } @end +#pragma mark - ASAsyncTransactionContainer + @implementation ASDisplayNode (ASAsyncTransactionContainer) - (BOOL)asyncdisplaykit_isAsyncTransactionContainer diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 8a3aa97b80..ae3f3c477a 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -12,18 +12,19 @@ // #import "_AS-objc-internal.h" -#import "ASDisplayNodeExtraIvars.h" #import "ASDisplayNode.h" #import "ASSentinel.h" #import "ASThread.h" -#import "ASLayoutOptions.h" #import "_ASTransitionContext.h" +#import "ASDisplayNodeLayoutContext.h" +#import "ASEnvironment.h" #include @protocol _ASDisplayLayerDelegate; @class _ASDisplayLayer; @class _ASPendingState; +@class ASSentinel; BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); @@ -68,6 +69,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo unsigned shouldRasterizeDescendants:1; unsigned shouldBypassEnsureDisplay:1; unsigned displaySuspended:1; + unsigned shouldAnimateSizeChanges:1; unsigned hasCustomDrawingPriority:1; // whether custom drawing is enabled @@ -89,45 +91,58 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo ASDisplayNode * __weak _supernode; ASSentinel *_displaySentinel; - ASSentinel *_replaceAsyncSentinel; + ASSentinel *_transitionSentinel; // This is the desired contentsScale, not the scale at which the layer's contents should be displayed CGFloat _contentsScaleForDisplay; - ASLayout *_previousLayout; + ASEnvironmentState _environmentState; ASLayout *_layout; - ASSizeRange _previousConstrainedSize; ASSizeRange _constrainedSize; UIEdgeInsets _hitTestSlop; NSMutableArray *_subnodes; + // Main thread only _ASTransitionContext *_transitionContext; BOOL _usesImplicitHierarchyManagement; - NSArray *_insertedSubnodes; - NSArray *_removedSubnodes; - std::vector _insertedSubnodePositions; - std::vector _removedSubnodePositions; - + int32_t _pendingTransitionID; + ASDisplayNodeLayoutContext *_pendingLayoutContext; + ASDisplayNodeViewBlock _viewBlock; ASDisplayNodeLayerBlock _layerBlock; ASDisplayNodeDidLoadBlock _nodeLoadedBlock; Class _viewClass; Class _layerClass; - + UIImage *_placeholderImage; CALayer *_placeholderLayer; // keeps track of nodes/subnodes that have not finished display, used with placeholders NSMutableSet *_pendingDisplayNodes; - ASDisplayNodeExtraIvars _extra; - ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext; ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext; + // Accessibility support + BOOL _isAccessibilityElement; + NSString *_accessibilityLabel; + NSString *_accessibilityHint; + NSString *_accessibilityValue; + UIAccessibilityTraits _accessibilityTraits; + CGRect _accessibilityFrame; + NSString *_accessibilityLanguage; + BOOL _accessibilityElementsHidden; + BOOL _accessibilityViewIsModal; + BOOL _shouldGroupAccessibilityChildren; + NSString *_accessibilityIdentifier; + UIAccessibilityNavigationStyle _accessibilityNavigationStyle; + NSArray *_accessibilityHeaderElements; + CGPoint _accessibilityActivationPoint; + UIBezierPath *_accessibilityPath; + #if TIME_DISPLAYNODE_OPS @public NSTimeInterval _debugTimeToCreateView; @@ -140,11 +155,13 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo + (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node; // The _ASDisplayLayer backing the node, if any. -@property (nonatomic, readonly, retain) _ASDisplayLayer *asyncLayer; +@property (nonatomic, readonly, strong) _ASDisplayLayer *asyncLayer; // Bitmask to check which methods an object overrides. @property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides; +@property (nonatomic, assign) CGRect threadSafeBounds; + // Swizzle to extend the builtin functionality with custom logic - (BOOL)__shouldLoadViewOrLayer; @@ -176,10 +193,10 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo - (void)displayImmediately; // Alternative initialiser for backing with a custom view class. Supports asynchronous display with _ASDisplayView subclasses. -- (id)initWithViewClass:(Class)viewClass; +- (instancetype)initWithViewClass:(Class)viewClass; // Alternative initialiser for backing with a custom layer class. Supports asynchronous display with _ASDisplayLayer subclasses. -- (id)initWithLayerClass:(Class)layerClass; +- (instancetype)initWithLayerClass:(Class)layerClass; @property (nonatomic, assign) CGFloat contentsScaleForDisplay; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.h b/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.h new file mode 100644 index 0000000000..2c5530cab2 --- /dev/null +++ b/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.h @@ -0,0 +1,33 @@ +// +// ASDisplayNodeLayoutContext.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 3/8/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASDimension.h" +#import "_ASTransitionContext.h" + +@class ASDisplayNode; +@class ASLayout; + +@interface ASDisplayNodeLayoutContext : NSObject <_ASTransitionContextLayoutDelegate> + +@property (nonatomic, readonly, weak) ASDisplayNode *node; +@property (nonatomic, readonly, strong) ASLayout *pendingLayout; +@property (nonatomic, readonly, assign) ASSizeRange pendingConstrainedSize; +@property (nonatomic, readonly, strong) ASLayout *previousLayout; +@property (nonatomic, readonly, assign) ASSizeRange previousConstrainedSize; + +- (instancetype)initWithNode:(ASDisplayNode *)node + pendingLayout:(ASLayout *)pendingLayout + pendingConstrainedSize:(ASSizeRange)pendingConstrainedSize + previousLayout:(ASLayout *)previousLayout + previousConstrainedSize:(ASSizeRange)previousConstrainedSize; + +- (void)applySubnodeInsertions; + +- (void)applySubnodeRemovals; + +@end diff --git a/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm b/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm new file mode 100644 index 0000000000..797657ac09 --- /dev/null +++ b/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm @@ -0,0 +1,183 @@ +// +// ASDisplayNodeLayoutContext.mm +// AsyncDisplayKit +// +// Created by Huy Nguyen on 3/8/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASDisplayNodeLayoutContext.h" + +#import "ASDisplayNode.h" +#import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+Subclasses.h" +#import "ASLayout.h" + +#import + +#import "NSArray+Diffing.h" +#import "ASEqualityHelpers.h" + +@implementation ASDisplayNodeLayoutContext { + ASDN::RecursiveMutex _propertyLock; + BOOL _calculatedSubnodeOperations; + NSArray *_insertedSubnodes; + NSArray *_removedSubnodes; + std::vector _insertedSubnodePositions; + std::vector _removedSubnodePositions; +} + +- (instancetype)initWithNode:(ASDisplayNode *)node + pendingLayout:(ASLayout *)pendingLayout + pendingConstrainedSize:(ASSizeRange)pendingConstrainedSize + previousLayout:(ASLayout *)previousLayout + previousConstrainedSize:(ASSizeRange)previousConstrainedSize +{ + self = [super init]; + if (self) { + _node = node; + _pendingLayout = pendingLayout; + _pendingConstrainedSize = pendingConstrainedSize; + _previousLayout = previousLayout; + _previousConstrainedSize = previousConstrainedSize; + } + return self; +} + +- (void)applySubnodeInsertions +{ + ASDN::MutexLocker l(_propertyLock); + [self calculateSubnodeOperationsIfNeeded]; + for (NSUInteger i = 0; i < [_insertedSubnodes count]; i++) { + NSUInteger p = _insertedSubnodePositions[i]; + [_node insertSubnode:_insertedSubnodes[i] atIndex:p]; + } +} + +- (void)applySubnodeRemovals +{ + ASDN::MutexLocker l(_propertyLock); + [self calculateSubnodeOperationsIfNeeded]; + for (NSUInteger i = 0; i < [_removedSubnodes count]; i++) { + [_removedSubnodes[i] removeFromSupernode]; + } +} + +- (void)calculateSubnodeOperationsIfNeeded +{ + ASDN::MutexLocker l(_propertyLock); + if (_calculatedSubnodeOperations) { + return; + } + if (_previousLayout) { + NSIndexSet *insertions, *deletions; + [_previousLayout.immediateSublayouts asdk_diffWithArray:_pendingLayout.immediateSublayouts + insertions:&insertions + deletions:&deletions + compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { + return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); + }]; + findNodesInLayoutAtIndexes(_pendingLayout, insertions, &_insertedSubnodes, &_insertedSubnodePositions); + findNodesInLayoutAtIndexesWithFilteredNodes(_previousLayout, + deletions, + _insertedSubnodes, + &_removedSubnodes, + &_removedSubnodePositions); + } else { + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_pendingLayout.immediateSublayouts count])]; + findNodesInLayoutAtIndexes(_pendingLayout, indexes, &_insertedSubnodes, &_insertedSubnodePositions); + _removedSubnodes = nil; + } + _calculatedSubnodeOperations = YES; +} + +#pragma mark - _ASTransitionContextDelegate + +- (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context +{ + ASDN::MutexLocker l(_propertyLock); + return _node.subnodes; +} + +- (NSArray *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context +{ + ASDN::MutexLocker l(_propertyLock); + [self calculateSubnodeOperationsIfNeeded]; + return _insertedSubnodes; +} + +- (NSArray *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context +{ + ASDN::MutexLocker l(_propertyLock); + [self calculateSubnodeOperationsIfNeeded]; + return _removedSubnodes; +} + +- (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key +{ + ASDN::MutexLocker l(_propertyLock); + if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { + return _previousLayout; + } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { + return _pendingLayout; + } else { + return nil; + } +} + +- (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key +{ + ASDN::MutexLocker l(_propertyLock); + if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { + return _previousConstrainedSize; + } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { + return _pendingConstrainedSize; + } else { + return ASSizeRangeMake(CGSizeZero, CGSizeZero); + } +} + +#pragma mark - Filter helpers + +/** + * @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector. + */ +static inline void findNodesInLayoutAtIndexes(ASLayout *layout, + NSIndexSet *indexes, + NSArray * __strong *storedNodes, + std::vector *storedPositions) +{ + findNodesInLayoutAtIndexesWithFilteredNodes(layout, indexes, nil, storedNodes, storedPositions); +} + +/** + * @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector. + * @discussion If the node exists in the `filteredNodes` array, the node is not added to `storedNodes`. + */ +static inline void findNodesInLayoutAtIndexesWithFilteredNodes(ASLayout *layout, + NSIndexSet *indexes, + NSArray *filteredNodes, + NSArray * __strong *storedNodes, + std::vector *storedPositions) +{ + NSMutableArray *nodes = [NSMutableArray array]; + std::vector positions = std::vector(); + NSUInteger idx = [indexes firstIndex]; + while (idx != NSNotFound) { + ASDisplayNode *node = (ASDisplayNode *)layout.immediateSublayouts[idx].layoutableObject; + ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts"); + // Ignore the odd case in which a non-node sublayout is accessed and the type cast fails + if (node != nil) { + BOOL notFiltered = (filteredNodes == nil || [filteredNodes indexOfObjectIdenticalTo:node] == NSNotFound); + if (notFiltered) { + [nodes addObject:node]; + positions.push_back(idx); + } + } + idx = [indexes indexGreaterThanIndex:idx]; + } + *storedNodes = nodes; + *storedPositions = positions; +} + +@end diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.h b/AsyncDisplayKit/Private/ASEnvironmentInternal.h new file mode 100644 index 0000000000..9bd2c305d4 --- /dev/null +++ b/AsyncDisplayKit/Private/ASEnvironmentInternal.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 "ASEnvironment.h" + +#pragma once + +BOOL ASEnvironmentStatePropagationEnabled(); + + +#pragma mark - Set and get extensible values for layout options + +void _ASEnvironmentLayoutOptionsExtensionSetBoolAtIndex(id object, int idx, BOOL value); +BOOL _ASEnvironmentLayoutOptionsExtensionGetBoolAtIndex(id object, int idx); + +void _ASEnvironmentLayoutOptionsExtensionSetIntegerAtIndex(id object, int idx, NSInteger value); +NSInteger _ASEnvironmentLayoutOptionsExtensionGetIntegerAtIndex(id object, int idx); + +void _ASEnvironmentLayoutOptionsExtensionSetEdgeInsetsAtIndex(id object, int idx, UIEdgeInsets value); +UIEdgeInsets _ASEnvironmentLayoutOptionsExtensionGetEdgeInsetsAtIndex(id object, int idx); + + +#pragma mark - Traversing an ASEnvironment Tree + +void ASEnvironmentPerformBlockOnObjectAndChildren(id object, void(^block)(id object)); +void ASEnvironmentPerformBlockOnObjectAndParents(id object, void(^block)(id object)); + + +#pragma mark - + +enum class ASEnvironmentStatePropagation { DOWN, UP }; + + +#pragma mark - Merging + +static const struct ASEnvironmentStateExtensions ASEnvironmentDefaultStateExtensions = {}; + +static const struct ASEnvironmentLayoutOptionsState ASEnvironmentDefaultLayoutOptionsState = {}; +ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentLayoutOptionsState state, ASEnvironmentStatePropagation propagation); + + +static const struct ASEnvironmentHierarchyState ASEnvironmentDefaultHierarchyState = {}; +ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentHierarchyState state, ASEnvironmentStatePropagation propagation); + + +#pragma mark - Propagation + +template +void ASEnvironmentStatePropagateDown(id object, ASEnvironmentStateType state) { + ASEnvironmentPerformBlockOnObjectAndChildren(object, ^(id node) { + object.environmentState = ASEnvironmentMergeObjectAndState(object.environmentState, state, ASEnvironmentStatePropagation::DOWN); + }); +} + +template +void ASEnvironmentStatePropagateUp(id object, ASEnvironmentStateType state) { + ASEnvironmentPerformBlockOnObjectAndParents(object, ^(id node) { + object.environmentState = ASEnvironmentMergeObjectAndState(object.environmentState, state, ASEnvironmentStatePropagation::UP); + }); +} + +template +void ASEnvironmentStateApply(id object, ASEnvironmentStateType& state, ASEnvironmentStatePropagation propagate) { + if (propagate == ASEnvironmentStatePropagation::DOWN) { + ASEnvironmentStatePropagateUp(object, state); + } else if (propagate == ASEnvironmentStatePropagation::UP) { + ASEnvironmentStatePropagateDown(object, state); + } +} diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm new file mode 100644 index 0000000000..83ea991b85 --- /dev/null +++ b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm @@ -0,0 +1,190 @@ +/* + * 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 "ASEnvironmentInternal.h" + +#import + +//#define LOG(...) NSLog(__VA_ARGS__) +#define LOG(...) + +#define AS_SUPPORT_PROPAGATION NO + +BOOL ASEnvironmentStatePropagationEnabled() +{ + return AS_SUPPORT_PROPAGATION; +} + + +#pragma mark - Traversing an ASEnvironment Tree + +void ASEnvironmentPerformBlockOnObjectAndChildren(id object, void(^block)(id node)) +{ + if (!object) { + return; + } + + std::queue> queue; + queue.push(object); + + while (!queue.empty()) { + id object = queue.front(); queue.pop(); + + block(object); + + for (id child in [object children]) { + queue.push(child); + } + } +} + +void ASEnvironmentPerformBlockOnObjectAndParents(id object, void(^block)(id node)) +{ + while (object) { + block(object); + object = [object parent]; + } +} + + +#pragma mark - Set and get extensible values from state structs + +void _ASEnvironmentLayoutOptionsExtensionSetBoolAtIndex(id object, int idx, BOOL value) +{ + NSCAssert(idx < kMaxEnvironmentStateBoolExtensions, @"Setting index outside of max bool extensions space"); + + ASEnvironmentState state = object.environmentState; + state.layoutOptionsState._extensions.boolExtensions[idx] = value; + object.environmentState = state; +} + +BOOL _ASEnvironmentLayoutOptionsExtensionGetBoolAtIndex(id object, int idx) +{ + NSCAssert(idx < kMaxEnvironmentStateBoolExtensions, @"Accessing index outside of max bool extensions space"); + return object.environmentState.layoutOptionsState._extensions.boolExtensions[idx]; +} + +void _ASEnvironmentLayoutOptionsExtensionSetIntegerAtIndex(id object, int idx, NSInteger value) +{ + NSCAssert(idx < kMaxEnvironmentStateIntegerExtensions, @"Setting index outside of max integer extensions space"); + + ASEnvironmentState state = object.environmentState; + state.layoutOptionsState._extensions.integerExtensions[idx] = value; + object.environmentState = state; +} + +NSInteger _ASEnvironmentLayoutOptionsExtensionGetIntegerAtIndex(id object, int idx) +{ + NSCAssert(idx < kMaxEnvironmentStateIntegerExtensions, @"Accessing index outside of max integer extensions space"); + return object.environmentState.layoutOptionsState._extensions.integerExtensions[idx]; +} + +void _ASEnvironmentLayoutOptionsExtensionSetEdgeInsetsAtIndex(id object, int idx, UIEdgeInsets value) +{ + NSCAssert(idx < kMaxEnvironmentStateEdgeInsetExtensions, @"Setting index outside of max edge insets extensions space"); + + ASEnvironmentState state = object.environmentState; + state.layoutOptionsState._extensions.edgeInsetsExtensions[idx] = value; + object.environmentState = state; +} + +UIEdgeInsets _ASEnvironmentLayoutOptionsExtensionGetEdgeInsetsAtIndex(id object, int idx) +{ + NSCAssert(idx < kMaxEnvironmentStateEdgeInsetExtensions, @"Accessing index outside of max edge insets extensions space"); + return object.environmentState.layoutOptionsState._extensions.edgeInsetsExtensions[idx]; +} + + +#pragma mark - Merging functions for states + +ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentHierarchyState hierarchyState, ASEnvironmentStatePropagation propagation) { + // Merge object and hierarchy state + LOG(@"Merge object and state: %@ - ASEnvironmentHierarchyState", object); + return environmentState; +} + +ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentLayoutOptionsState layoutOptionsState, ASEnvironmentStatePropagation propagation) { + // Merge object and layout options state + LOG(@"Merge object and state: %@ - ASEnvironmentLayoutOptionsState", object); + + if (!ASEnvironmentStatePropagationEnabled()) { + return environmentState; + } + + // Support propagate up + if (propagation == ASEnvironmentStatePropagation::UP) { + + // Object is the parent and the state is the state of the child + const ASEnvironmentLayoutOptionsState defaultState = ASEnvironmentDefaultLayoutOptionsState; + ASEnvironmentLayoutOptionsState parentLayoutOptionsState = environmentState.layoutOptionsState; + + // For every field check if the parent value is equal to the default and if so propegate up the value of the passed + // in layout options state + if (parentLayoutOptionsState.spacingBefore == defaultState.spacingBefore) { + parentLayoutOptionsState.spacingBefore = layoutOptionsState.spacingBefore; + } + if (parentLayoutOptionsState.spacingAfter == defaultState.spacingAfter) { + parentLayoutOptionsState.spacingAfter = layoutOptionsState.spacingAfter; + } + if (parentLayoutOptionsState.alignSelf == defaultState.alignSelf) { + parentLayoutOptionsState.alignSelf = layoutOptionsState.alignSelf; + } + if (parentLayoutOptionsState.flexGrow == defaultState.flexGrow) { + parentLayoutOptionsState.flexGrow = layoutOptionsState.flexGrow; + } + if (ASRelativeDimensionEqualToRelativeDimension(parentLayoutOptionsState.flexBasis, defaultState.flexBasis)) { + parentLayoutOptionsState.flexBasis = layoutOptionsState.flexBasis; + } + if (parentLayoutOptionsState.alignSelf == defaultState.alignSelf) { + parentLayoutOptionsState.alignSelf = layoutOptionsState.alignSelf; + } + if (parentLayoutOptionsState.ascender == defaultState.ascender) { + parentLayoutOptionsState.ascender = layoutOptionsState.ascender; + } + + if (ASRelativeSizeRangeEqualToRelativeSizeRange(parentLayoutOptionsState.sizeRange, defaultState.sizeRange)) { + // For now it is unclear if we should be up-propagating sizeRange or layoutPosition. + // parentLayoutOptionsState.sizeRange = layoutOptionsState.sizeRange; + } + if (CGPointEqualToPoint(parentLayoutOptionsState.layoutPosition, defaultState.layoutPosition)) { + // For now it is unclear if we should be up-propagating sizeRange or layoutPosition. + // parentLayoutOptionsState.layoutPosition = layoutOptionsState.layoutPosition; + } + + // Merge extended values if necessary + const ASEnvironmentStateExtensions defaultExtensions = ASEnvironmentDefaultStateExtensions; + const ASEnvironmentStateExtensions layoutOptionsStateExtensions = layoutOptionsState._extensions; + ASEnvironmentStateExtensions parentLayoutOptionsExtensions = parentLayoutOptionsState._extensions; + + for (int i = 0; i < kMaxEnvironmentStateBoolExtensions; i++) { + if (parentLayoutOptionsExtensions.boolExtensions[i] == defaultExtensions.boolExtensions[i]) { + parentLayoutOptionsExtensions.boolExtensions[i] = layoutOptionsStateExtensions.boolExtensions[i]; + } + } + + for (int i = 0; i < kMaxEnvironmentStateIntegerExtensions; i++) { + if (parentLayoutOptionsExtensions.integerExtensions[i] == defaultExtensions.integerExtensions[i]) { + parentLayoutOptionsExtensions.integerExtensions[i] = layoutOptionsStateExtensions.integerExtensions[i]; + } + } + + for (int i = 0; i < kMaxEnvironmentStateEdgeInsetExtensions; i++) { + if (UIEdgeInsetsEqualToEdgeInsets(parentLayoutOptionsExtensions.edgeInsetsExtensions[i], defaultExtensions.edgeInsetsExtensions[i])) { + parentLayoutOptionsExtensions.edgeInsetsExtensions[i] = layoutOptionsStateExtensions.edgeInsetsExtensions[i]; + } + } + parentLayoutOptionsState._extensions = parentLayoutOptionsExtensions; + + // Update layout options state + environmentState.layoutOptionsState = parentLayoutOptionsState; + } + + return environmentState; +} diff --git a/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h b/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h new file mode 100644 index 0000000000..2814ef9ddc --- /dev/null +++ b/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h @@ -0,0 +1,27 @@ +// +// ASImageNode+AnimatedImagePrivate.h +// AsyncDisplayKit +// +// Created by Garrett Moon on 3/30/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASThread.h" + +@interface ASImageNode () +{ + ASDN::RecursiveMutex _animatedImageLock; + ASDN::RecursiveMutex _animatedImagePausedLock; + ASDN::Mutex _displayLinkLock; + id _animatedImage; + BOOL _animatedImagePaused; + CADisplayLink *_displayLink; + + //accessed on main thread only + CFTimeInterval _playHead; + NSUInteger _playedLoops; +} + +@property (atomic, assign) CFTimeInterval lastDisplayLinkFire; + +@end diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.mm b/AsyncDisplayKit/Private/ASInternalHelpers.mm index 0f6aa6f08d..40f6445434 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.mm +++ b/AsyncDisplayKit/Private/ASInternalHelpers.mm @@ -34,19 +34,6 @@ BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL sele return (superclassIMP != subclassIMP); } -static void ASDispatchOnceOnMainThread(dispatch_once_t *predicate, dispatch_block_t block) -{ - if (ASDisplayNodeThreadIsMain()) { - dispatch_once(predicate, block); - } else { - if (DISPATCH_EXPECT(*predicate == 0L, NO)) { - dispatch_sync(dispatch_get_main_queue(), ^{ - dispatch_once(predicate, block); - }); - } - } -} - void ASPerformBlockOnMainThread(void (^block)()) { if (ASDisplayNodeThreadIsMain()) { @@ -67,12 +54,13 @@ void ASPerformBlockOnBackgroundThread(void (^block)()) CGFloat ASScreenScale() { - static CGFloat _scale; + static CGFloat __scale = 0.0; static dispatch_once_t onceToken; - ASDispatchOnceOnMainThread(&onceToken, ^{ - _scale = [UIScreen mainScreen].scale; + dispatch_once(&onceToken, ^{ + ASDisplayNodeCAssertMainThread(); + __scale = [[UIScreen mainScreen] scale]; }); - return _scale; + return __scale; } CGFloat ASFloorPixelValue(CGFloat f) diff --git a/AsyncDisplayKit/Private/ASLayoutOptionsPrivate.h b/AsyncDisplayKit/Private/ASLayoutOptionsPrivate.h deleted file mode 100644 index 34a26da4f2..0000000000 --- a/AsyncDisplayKit/Private/ASLayoutOptionsPrivate.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import -#import -#import - -@interface ASDisplayNode(ASLayoutOptions) -@end - -@interface ASDisplayNode() -{ - ASLayoutOptions *_layoutOptions; - ASDN::RecursiveMutex _layoutOptionsLock; -} -@end - -@interface ASLayoutSpec(ASLayoutOptions) -@end - -@interface ASLayoutSpec() -{ - ASLayoutOptions *_layoutOptions; - ASDN::RecursiveMutex _layoutOptionsLock; -} -@end diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h index d5058a8622..756b1cc775 100644 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h +++ b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h @@ -18,8 +18,14 @@ ASDISPLAYNODE_EXTERN_C_BEGIN /** - * Deep muutable copy of multidimensional array. - * It will recursively do the multiple copy for each subarray. + * Deep mutable copy of an array that contains arrays, which contain objects. It will go one level deep into the array to copy. + * This method is substantially faster than the generalized version, e.g. about 10x faster, so use it whenever it fits the need. + */ +extern NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array); + +/** + * Deep mutable copy of multidimensional array. This is completely generalized and supports copying mixed-depth arrays, + * where some subarrays might contain both elements and other subarrays. It will recursively do the multiple copy for each subarray. */ extern NSObject *ASMultidimensionalArrayDeepMutableCopy(NSObject *obj); @@ -44,7 +50,12 @@ extern NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray extern NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *MultidimensionalArray, NSIndexSet *indexSet); /** - * Return all the index paths of mutable multidimensional array, in ascending order. + * Return all the index paths of a two-dimensional array, in ascending order. + */ +extern NSArray *ASIndexPathsForTwoDimensionalArray(NSArray * twoDimensionalArray); + +/** + * Return all the index paths of a multidimensional array, in ascending order. */ extern NSArray *ASIndexPathsForMultidimensionalArray(NSArray *MultidimensionalArray); diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm index af0f4991ef..f939410d6d 100644 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm +++ b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm @@ -16,7 +16,8 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray NSUInteger &curIdx, NSIndexPath *curIndexPath, const NSUInteger dimension, - void (^updateBlock)(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx)) { + void (^updateBlock)(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx)) +{ if (curIdx == indexPaths.count) { return; } @@ -38,23 +39,27 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray } } -static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, NSIndexPath *curIndexPath, NSMutableArray *res) { +static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, NSIndexPath *curIndexPath, NSMutableArray *res) +{ if (![obj isKindOfClass:[NSArray class]]) { [res addObject:curIndexPath]; } else { - NSArray *arr = (NSArray *)obj; - [arr enumerateObjectsUsingBlock:^(NSObject *subObj, NSUInteger idx, BOOL *stop) { - ASRecursivelyFindIndexPathsForMultidimensionalArray(subObj, [curIndexPath indexPathByAddingIndex:idx], res); - }]; + NSArray *array = (NSArray *)obj; + NSUInteger idx = 0; + for (NSArray *subarray in array) { + ASRecursivelyFindIndexPathsForMultidimensionalArray(subarray, [curIndexPath indexPathByAddingIndex:idx], res); + idx++; + } } } #pragma mark - Public Methods -NSObject *ASMultidimensionalArrayDeepMutableCopy(NSObject *obj) { +NSObject *ASMultidimensionalArrayDeepMutableCopy(NSObject *obj) +{ if ([obj isKindOfClass:[NSArray class]]) { NSArray *arr = (NSArray *)obj; - NSMutableArray * mutableArr = [NSMutableArray array]; + NSMutableArray * mutableArr = [NSMutableArray arrayWithCapacity:arr.count]; for (NSObject *elem in arr) { [mutableArr addObject:ASMultidimensionalArrayDeepMutableCopy(elem)]; } @@ -64,7 +69,18 @@ NSObject *ASMultidimensionalArrayDeepMutableCopy(NSObject return obj; } -void ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths, NSArray *elements) { +NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) +{ + NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; + for (NSArray *subarray in array) { + ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); + [newArray addObject:[subarray mutableCopy]]; + } + return newArray; +} + +void ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths, NSArray *elements) +{ ASDisplayNodeCAssert(indexPaths.count == elements.count, @"Inconsistent indexPaths and elements"); if (!indexPaths.count) { @@ -81,7 +97,8 @@ void ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(NSMutableArray *mutab ASDisplayNodeCAssert(curIdx == indexPaths.count, @"Indexpath is out of range !"); } -void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) { +void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) +{ if (!indexPaths.count) { return; } @@ -96,7 +113,8 @@ void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutable ASDisplayNodeCAssert(curIdx == indexPaths.count, @"Indexpath is out of range !"); } -NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) { +NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) +{ NSUInteger curIdx = 0; NSIndexPath *indexPath = [[NSIndexPath alloc] init]; NSMutableArray *deletedElements = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; @@ -114,8 +132,9 @@ NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutab return deletedElements; } -NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensionalArray, NSIndexSet *indexSet) { - NSMutableArray *res = [[NSMutableArray alloc] initWithCapacity:multidimensionalArray.count]; +NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensionalArray, NSIndexSet *indexSet) +{ + NSMutableArray *res = [NSMutableArray array]; [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { ASRecursivelyFindIndexPathsForMultidimensionalArray(multidimensionalArray[idx], [NSIndexPath indexPathWithIndex:idx], res); }]; @@ -123,9 +142,24 @@ NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensiona return res; } -NSArray *ASIndexPathsForMultidimensionalArray(NSArray *multidimensionalArray) { - NSMutableArray *res = [NSMutableArray arrayWithCapacity:multidimensionalArray.count]; +NSArray *ASIndexPathsForTwoDimensionalArray(NSArray * twoDimensionalArray) +{ + NSMutableArray *result = [NSMutableArray array]; + NSUInteger section = 0; + for (NSArray *subarray in twoDimensionalArray) { + ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); + NSUInteger itemCount = subarray.count; + for (NSUInteger item = 0; item < itemCount; item++) { + [result addObject:[NSIndexPath indexPathWithIndexes:(const NSUInteger []){ section, item } length:2]]; + } + section++; + } + return result; +} + +NSArray *ASIndexPathsForMultidimensionalArray(NSArray *multidimensionalArray) +{ + NSMutableArray *res = [NSMutableArray array]; ASRecursivelyFindIndexPathsForMultidimensionalArray(multidimensionalArray, [[NSIndexPath alloc] init], res); return res; } - diff --git a/AsyncDisplayKit/Private/ASPendingStateController.mm b/AsyncDisplayKit/Private/ASPendingStateController.mm index 113af7dc66..4f2574a786 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.mm +++ b/AsyncDisplayKit/Private/ASPendingStateController.mm @@ -58,22 +58,18 @@ [self scheduleFlushIfNeeded]; } -/** - * NOTE: There is a small re-entrancy hazard here. - * If the user gives us a subclass of UIView/CALayer that - * adds side-effects to property sets, and one side effect - * waits on a background thread that sets a view/layer property - * on a loaded node, then we've got a deadlock. - */ - (void)flush { ASDisplayNodeAssertMainThread(); - ASDN::MutexLocker l(_lock); - for (ASDisplayNode *node in _dirtyNodes) { + _lock.lock(); + ASWeakSet *dirtyNodes = _dirtyNodes; + _dirtyNodes = [[ASWeakSet alloc] init]; + _flags.pendingFlush = NO; + _lock.unlock(); + + for (ASDisplayNode *node in dirtyNodes) { [node applyPendingViewState]; } - [_dirtyNodes removeAllObjects]; - _flags.pendingFlush = NO; } diff --git a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm index 57fd1b4c6d..46df924adf 100644 --- a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm @@ -12,7 +12,6 @@ #import "ASLayoutSpecUtilities.h" #import "ASStackLayoutSpecUtilities.h" -#import "ASLayoutOptions.h" static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style, const ASLayout *layout) { @@ -72,7 +71,7 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A baseline will be larger so we will keep that as the max baseline. However, if were to align from the last baseline we'd find the max baseline by taking the height of node and adding - the font's descender (its negative). In the case of the first node, which is only 1 line, this should be the same value as the ascender. + the font's descender (it's negative). In the case of the first node, which is only 1 line, this should be the same value as the ascender. The second node, however, has a larger height and there will have a larger baseline offset. */ const auto baselineIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){ @@ -87,7 +86,7 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A for each node is our computed maxAscender - node.ascender. If the 16pt node had an ascender of 10 and the 14pt node had an ascender of 8, that means we will offset the 14pt node by 2 pts. - Note: if we are alinging to the last baseline, then we don't need this value in our computation. However, we do want + Note: if we are aligning to the last baseline, then we don't need this value in our computation. However, we do want our layoutSpec to have it so that it can be baseline aligned with another text node or baseline layout spec. */ const auto ascenderIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){ @@ -144,7 +143,7 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A font size of 40 (max ascender). Now, we have to move the node with multiple lines down to the other node's baseline. This node with multiple lines will extend below the first node farther than it did before aligning the baselines thus increasing the cross size. - After finding the new cross size, we need to clamp it so that it fits within the constrainted size. + After finding the new cross size, we need to clamp it so that it fits within the constrained size. */ const auto it = std::max_element(stackedChildren.begin(), stackedChildren.end(), diff --git a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h index ae018a1f87..1045bb8501 100644 --- a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h +++ b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h @@ -72,11 +72,11 @@ inline ASStackLayoutAlignItems alignment(ASStackLayoutAlignSelf childAlignment, inline ASStackLayoutAlignItems alignment(ASHorizontalAlignment alignment, ASStackLayoutAlignItems defaultAlignment) { switch (alignment) { - case ASAlignmentLeft: + case ASHorizontalAlignmentLeft: return ASStackLayoutAlignItemsStart; - case ASAlignmentMiddle: + case ASHorizontalAlignmentMiddle: return ASStackLayoutAlignItemsCenter; - case ASAlignmentRight: + case ASHorizontalAlignmentRight: return ASStackLayoutAlignItemsEnd; case ASHorizontalAlignmentNone: default: @@ -87,11 +87,11 @@ inline ASStackLayoutAlignItems alignment(ASHorizontalAlignment alignment, ASStac inline ASStackLayoutAlignItems alignment(ASVerticalAlignment alignment, ASStackLayoutAlignItems defaultAlignment) { switch (alignment) { - case ASAlignmentTop: + case ASVerticalAlignmentTop: return ASStackLayoutAlignItemsStart; - case ASAlignmentCenter: + case ASVerticalAlignmentCenter: return ASStackLayoutAlignItemsCenter; - case ASAlignmentBottom: + case ASVerticalAlignmentBottom: return ASStackLayoutAlignItemsEnd; case ASVerticalAlignmentNone: default: @@ -102,11 +102,11 @@ inline ASStackLayoutAlignItems alignment(ASVerticalAlignment alignment, ASStackL inline ASStackLayoutJustifyContent justifyContent(ASHorizontalAlignment alignment, ASStackLayoutJustifyContent defaultJustifyContent) { switch (alignment) { - case ASAlignmentLeft: + case ASHorizontalAlignmentLeft: return ASStackLayoutJustifyContentStart; - case ASAlignmentMiddle: + case ASHorizontalAlignmentMiddle: return ASStackLayoutJustifyContentCenter; - case ASAlignmentRight: + case ASHorizontalAlignmentRight: return ASStackLayoutJustifyContentEnd; case ASHorizontalAlignmentNone: default: @@ -117,11 +117,11 @@ inline ASStackLayoutJustifyContent justifyContent(ASHorizontalAlignment alignmen inline ASStackLayoutJustifyContent justifyContent(ASVerticalAlignment alignment, ASStackLayoutJustifyContent defaultJustifyContent) { switch (alignment) { - case ASAlignmentTop: + case ASVerticalAlignmentTop: return ASStackLayoutJustifyContentStart; - case ASAlignmentCenter: + case ASVerticalAlignmentCenter: return ASStackLayoutJustifyContentCenter; - case ASAlignmentBottom: + case ASVerticalAlignmentBottom: return ASStackLayoutJustifyContentEnd; case ASVerticalAlignmentNone: default: diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index 428319d317..cd18dd2157 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -14,7 +14,6 @@ #import "ASLayoutSpecUtilities.h" #import "ASStackLayoutSpecUtilities.h" #import "ASLayoutable.h" -#import "ASLayoutOptions.h" #import "ASAssert.h" static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm index a19ae298ba..57902a6b4e 100644 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm @@ -14,7 +14,6 @@ #import "ASLayoutSpecUtilities.h" #import "ASStackLayoutSpecUtilities.h" -#import "ASLayoutOptions.h" /** Sizes the child given the parameters specified, and returns the computed layout. @@ -116,8 +115,7 @@ static CGFloat computeStackDimensionSum(const std::vector child = l.child; - const ASLayoutOptions *layoutOptions = child.layoutOptions; - return x + layoutOptions.spacingBefore + layoutOptions.spacingAfter; + return x + child.spacingBefore + child.spacingAfter; }); // Sum up the childrens' dimensions (including spacing) in the stack direction. diff --git a/AsyncDisplayKit/Private/ASWeakSet.h b/AsyncDisplayKit/Private/ASWeakSet.h index 8f6a6576ca..2a72b19e9d 100644 --- a/AsyncDisplayKit/Private/ASWeakSet.h +++ b/AsyncDisplayKit/Private/ASWeakSet.h @@ -27,6 +27,9 @@ NS_ASSUME_NONNULL_BEGIN /// Removes all objects from the set. - (void)removeAllObjects; +/// Returns a standard *retained* NSArray of all objects. Not free to generate, but useful for iterating over contents. +- (NSArray *)allObjects; + /** How many objects are contained in this set. diff --git a/AsyncDisplayKit/Private/ASWeakSet.m b/AsyncDisplayKit/Private/ASWeakSet.m index 9ac3020e74..95c4ad1275 100644 --- a/AsyncDisplayKit/Private/ASWeakSet.m +++ b/AsyncDisplayKit/Private/ASWeakSet.m @@ -7,6 +7,7 @@ // #import "ASWeakSet.h" +#import @interface ASWeakSet<__covariant ObjectType> () @property (nonatomic, strong, readonly) NSMapTable *mapTable; @@ -25,7 +26,7 @@ - (void)addObject:(id)object { - [_mapTable setObject:[NSNull null] forKey:object]; + [_mapTable setObject:(NSNull *)kCFNull forKey:object]; } - (void)removeObject:(id)object @@ -38,6 +39,22 @@ [_mapTable removeAllObjects]; } +- (NSArray *)allObjects +{ + // We use keys instead of values in the map table for efficiency and better characteristics when the keys are deallocated. + // Documentation is currently unclear on whether -keyEnumerator retains its values, but does imply that modifying a + // mutable collection is still not safe while enumerating that way - which is one of the main uses for this method. + // A helper function called NSAllMapTableKeys() might do exactly what we want and should be more efficient, but unfortunately + // is throwing a strange compiler error and may not be available in practice on the latest iOS version. + // Lastly, even -dictionaryRepresentation and then -allKeys won't work, because it attempts to copy the values of each key, + // which may not support copying (such as ASRangeControllers). + NSMutableArray *allObjects = [NSMutableArray array]; + for (id object in _mapTable) { + [allObjects addObject:object]; + } + return allObjects; +} + - (BOOL)containsObject:(id)object { return [_mapTable objectForKey:object] != nil; @@ -73,4 +90,9 @@ return [_mapTable countByEnumeratingWithState:state objects:buffer count:len]; } +- (NSString *)description +{ + return [[super description] stringByAppendingFormat:@" count: %lu, contents: %@", (unsigned long)self.count, _mapTable]; +} + @end diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.m b/AsyncDisplayKit/Private/NSArray+Diffing.m index 00893d1416..837d1ed25d 100644 --- a/AsyncDisplayKit/Private/NSArray+Diffing.m +++ b/AsyncDisplayKit/Private/NSArray+Diffing.m @@ -19,6 +19,7 @@ - (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions compareBlock:(BOOL (^)(id lhs, id rhs))comparison { + NSAssert(comparison != nil, @"Comparison block is required"); NSIndexSet *commonIndexes = [self _asdk_commonIndexesWithArray:array compareBlock:comparison]; if (insertions) { @@ -48,30 +49,37 @@ - (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL (^)(id lhs, id rhs))comparison { - NSInteger lengths[self.count+1][array.count+1]; - for (NSInteger i = self.count; i >= 0; i--) { - for (NSInteger j = array.count; j >= 0; j--) { - if (i == self.count || j == array.count) { + NSAssert(comparison != nil, @"Comparison block is required"); + + NSInteger selfCount = self.count; + NSInteger arrayCount = array.count; + + NSInteger lengths[selfCount+1][arrayCount+1]; + for (NSInteger i = 0; i <= selfCount; i++) { + for (NSInteger j = 0; j <= arrayCount; j++) { + if (i == 0 || j == 0) { lengths[i][j] = 0; - } else if ([self[i] isEqual:array[j]]) { - lengths[i][j] = 1 + lengths[i+1][j+1]; + } else if (comparison(self[i-1], array[j-1])) { + lengths[i][j] = 1 + lengths[i-1][j-1]; } else { - lengths[i][j] = MAX(lengths[i+1][j], lengths[i][j+1]); + lengths[i][j] = MAX(lengths[i-1][j], lengths[i][j-1]); } } } NSMutableIndexSet *common = [NSMutableIndexSet indexSet]; - for (NSInteger i = 0, j = 0; i < self.count && j < array.count;) { - if (comparison(self[i], array[j])) { - [common addIndex:i]; - i++; j++; - } else if (lengths[i+1][j] >= lengths[i][j+1]) { - i++; + NSInteger i = selfCount, j = arrayCount; + while(i > 0 && j > 0) { + if (comparison(self[i-1], array[j-1])) { + [common addIndex:(i-1)]; + i--; j--; + } else if (lengths[i-1][j] > lengths[i][j-1]) { + i--; } else { - j++; + j--; } } + return common; } diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/AsyncDisplayKit/Private/_ASPendingState.mm index ddb827f28c..b85cbb57c3 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -67,6 +67,10 @@ typedef struct { int setAccessibilityViewIsModal:1; int setShouldGroupAccessibilityChildren:1; int setAccessibilityIdentifier:1; + int setAccessibilityNavigationStyle:1; + int setAccessibilityHeaderElements:1; + int setAccessibilityActivationPoint:1; + int setAccessibilityPath:1; } ASPendingStateFlags; @implementation _ASPendingState @@ -106,6 +110,10 @@ typedef struct { BOOL accessibilityViewIsModal; BOOL shouldGroupAccessibilityChildren; NSString *accessibilityIdentifier; + UIAccessibilityNavigationStyle accessibilityNavigationStyle; + NSArray *accessibilityHeaderElements; + CGPoint accessibilityActivationPoint; + UIBezierPath *accessibilityPath; ASPendingStateFlags _flags; } @@ -171,7 +179,7 @@ ASDISPLAYNODE_INLINE void ASPendingStateApplyMetricsToLayer(_ASPendingState *sta static CGColorRef blackColorRef = NULL; static UIColor *defaultTintColor = nil; -- (id)init +- (instancetype)init { if (!(self = [super init])) return nil; @@ -226,6 +234,10 @@ static UIColor *defaultTintColor = nil; accessibilityViewIsModal = NO; shouldGroupAccessibilityChildren = NO; accessibilityIdentifier = nil; + accessibilityNavigationStyle = UIAccessibilityNavigationStyleAutomatic; + accessibilityHeaderElements = nil; + accessibilityActivationPoint = CGPointZero; + accessibilityPath = nil; edgeAntialiasingMask = (kCALayerLeftEdge | kCALayerRightEdge | kCALayerTopEdge | kCALayerBottomEdge); return self; @@ -291,6 +303,11 @@ static UIColor *defaultTintColor = nil; - (void)setBounds:(CGRect)newBounds { + ASDisplayNodeAssert(!isnan(newBounds.size.width) && !isnan(newBounds.size.height), @"Invalid bounds %@ provided to %@", NSStringFromCGRect(newBounds), self); + if (isnan(newBounds.size.width)) + newBounds.size.width = 0.0; + if (isnan(newBounds.size.height)) + newBounds.size.height = 0.0; bounds = newBounds; _flags.setBounds = YES; } @@ -359,6 +376,11 @@ static UIColor *defaultTintColor = nil; - (void)setPosition:(CGPoint)newPosition { + ASDisplayNodeAssert(!isnan(newPosition.x) && !isnan(newPosition.y), @"Invalid position %@ provided to %@", NSStringFromCGPoint(newPosition), self); + if (isnan(newPosition.x)) + newPosition.x = 0.0; + if (isnan(newPosition.y)) + newPosition.y = 0.0; position = newPosition; _flags.setPosition = YES; } @@ -584,6 +606,59 @@ static UIColor *defaultTintColor = nil; } } +- (UIAccessibilityNavigationStyle)accessibilityNavigationStyle +{ + return accessibilityNavigationStyle; +} + +- (void)setAccessibilityNavigationStyle:(UIAccessibilityNavigationStyle)newAccessibilityNavigationStyle +{ + _flags.setAccessibilityNavigationStyle = YES; + accessibilityNavigationStyle = newAccessibilityNavigationStyle; +} + +- (NSArray *)accessibilityHeaderElements +{ + return accessibilityHeaderElements; +} + +- (void)setAccessibilityHeaderElements:(NSArray *)newAccessibilityHeaderElements +{ + _flags.setAccessibilityHeaderElements = YES; + if (accessibilityHeaderElements != newAccessibilityHeaderElements) { + accessibilityHeaderElements = [newAccessibilityHeaderElements copy]; + } +} + +- (CGPoint)accessibilityActivationPoint +{ + if (_flags.setAccessibilityActivationPoint) { + return accessibilityActivationPoint; + } + + // Default == Mid-point of the accessibilityFrame + return CGPointMake(CGRectGetMidX(accessibilityFrame), CGRectGetMidY(accessibilityFrame)); +} + +- (void)setAccessibilityActivationPoint:(CGPoint)newAccessibilityActivationPoint +{ + _flags.setAccessibilityActivationPoint = YES; + accessibilityActivationPoint = newAccessibilityActivationPoint; +} + +- (UIBezierPath *)accessibilityPath +{ + return accessibilityPath; +} + +- (void)setAccessibilityPath:(UIBezierPath *)newAccessibilityPath +{ + _flags.setAccessibilityPath = YES; + if (accessibilityPath != newAccessibilityPath) { + accessibilityPath = newAccessibilityPath; + } +} + - (void)applyToLayer:(CALayer *)layer { ASPendingStateFlags flags = _flags; @@ -817,6 +892,20 @@ static UIColor *defaultTintColor = nil; if (flags.setAccessibilityIdentifier) view.accessibilityIdentifier = accessibilityIdentifier; + + if (flags.setAccessibilityNavigationStyle) + view.accessibilityNavigationStyle = accessibilityNavigationStyle; + +#if TARGET_OS_TV + if (flags.setAccessibilityHeaderElements) + view.accessibilityHeaderElements = accessibilityHeaderElements; +#endif + + if (flags.setAccessibilityActivationPoint) + view.accessibilityActivationPoint = accessibilityActivationPoint; + + if (flags.setAccessibilityPath) + view.accessibilityPath = accessibilityPath; // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: if (flags.setFrame && setFrameDirectly) { @@ -916,6 +1005,12 @@ static UIColor *defaultTintColor = nil; pendingState.accessibilityViewIsModal = view.accessibilityViewIsModal; pendingState.shouldGroupAccessibilityChildren = view.shouldGroupAccessibilityChildren; pendingState.accessibilityIdentifier = view.accessibilityIdentifier; + pendingState.accessibilityNavigationStyle = view.accessibilityNavigationStyle; +#if TARGET_OS_TV + pendingState.accessibilityHeaderElements = view.accessibilityHeaderElements; +#endif + pendingState.accessibilityActivationPoint = view.accessibilityActivationPoint; + pendingState.accessibilityPath = view.accessibilityPath; return pendingState; } @@ -982,7 +1077,11 @@ static UIColor *defaultTintColor = nil; || flags.setAccessibilityElementsHidden || flags.setAccessibilityViewIsModal || flags.setShouldGroupAccessibilityChildren - || flags.setAccessibilityIdentifier); + || flags.setAccessibilityIdentifier + || flags.setAccessibilityNavigationStyle + || flags.setAccessibilityHeaderElements + || flags.setAccessibilityActivationPoint + || flags.setAccessibilityPath); } - (void)dealloc diff --git a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h index fab9cdf7cc..49dc9b92f3 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h +++ b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h @@ -8,10 +8,10 @@ * */ -#import +#pragma once -#ifndef ComponentKit_ASTextKitAttributes_h -#define ComponentKit_ASTextKitAttributes_h +#import +#import "ASEqualityHelpers.h" @protocol ASTextKitTruncating; @@ -22,11 +22,6 @@ extern NSString *const ASTextKitTruncationAttributeName; */ extern NSString *const ASTextKitEntityAttributeName; -static inline BOOL _objectsEqual(id obj1, id obj2) -{ - return obj1 == obj2 ? YES : [obj1 isEqual:obj2]; -} - /** All NSObject values in this struct should be copied when passed into the TextComponent. */ @@ -86,10 +81,6 @@ struct ASTextKitAttributes { An array of scale factors in descending order to apply to the text to try to make it fit into a constrained size. */ NSArray *pointSizeScaleFactors; - /** - The currently applied scale factor. Only valid if pointSizeScaleFactors are provided. Defaults to 0 (no scaling) - */ - CGFloat currentScaleFactor; /** An optional block that returns a custom layout manager subclass. If nil, defaults to NSLayoutManager. */ @@ -123,7 +114,6 @@ struct ASTextKitAttributes { shadowOpacity, shadowRadius, pointSizeScaleFactors, - currentScaleFactor, layoutManagerCreationBlock, layoutManagerDelegate, textStorageCreationBlock, @@ -138,18 +128,15 @@ struct ASTextKitAttributes { && shadowOpacity == other.shadowOpacity && shadowRadius == other.shadowRadius && [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors] - && currentScaleFactor == currentScaleFactor && layoutManagerCreationBlock == other.layoutManagerCreationBlock && textStorageCreationBlock == other.textStorageCreationBlock && CGSizeEqualToSize(shadowOffset, other.shadowOffset) - && _objectsEqual(exclusionPaths, other.exclusionPaths) - && _objectsEqual(avoidTailTruncationSet, other.avoidTailTruncationSet) - && _objectsEqual(shadowColor, other.shadowColor) - && _objectsEqual(attributedString, other.attributedString) - && _objectsEqual(truncationAttributedString, other.truncationAttributedString); + && ASObjectIsEqual(exclusionPaths, other.exclusionPaths) + && ASObjectIsEqual(avoidTailTruncationSet, other.avoidTailTruncationSet) + && ASObjectIsEqual(shadowColor, other.shadowColor) + && ASObjectIsEqual(attributedString, other.attributedString) + && ASObjectIsEqual(truncationAttributedString, other.truncationAttributedString); } size_t hash() const; }; - -#endif diff --git a/AsyncDisplayKit/TextKit/ASTextKitHelpers.h b/AsyncDisplayKit/TextKit/ASTextKitComponents.h similarity index 66% rename from AsyncDisplayKit/TextKit/ASTextKitHelpers.h rename to AsyncDisplayKit/TextKit/ASTextKitComponents.h index 12ec10472a..17f6c1f9cc 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitHelpers.h +++ b/AsyncDisplayKit/TextKit/ASTextKitComponents.h @@ -6,11 +6,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import - -#import #import -#import "ASBaseDefines.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -37,12 +34,23 @@ ASDISPLAYNODE_INLINE CGSize ceilSizeValue(CGSize s) @return An `ASTextKitComponents` containing the created components. The text view component will be nil. @discussion The returned components will be hooked up together, so they are ready for use as a system upon return. */ -+ (ASTextKitComponents *)componentsWithAttributedSeedString:(nullable NSAttributedString *)attributedSeedString - textContainerSize:(CGSize)textContainerSize; ++ (instancetype)componentsWithAttributedSeedString:(nullable NSAttributedString *)attributedSeedString + textContainerSize:(CGSize)textContainerSize; + +/** + @abstract Creates the stack of TextKit components. + @param textStorage The NSTextStorage to use. + @param textContainerSize The size of the text-container. Typically, size specifies the constraining width of the layout, and FLT_MAX for height. Pass CGSizeZero if these components will be hooked up to a UITextView, which will manage the text container's size itself. + @param layoutManager The NSLayoutManager to use. + @return An `ASTextKitComponents` containing the created components. The text view component will be nil. + @discussion The returned components will be hooked up together, so they are ready for use as a system upon return. + */ ++ (instancetype)componentsWithTextStorage:(NSTextStorage *)textStorage + textContainerSize:(CGSize)textContainerSize + layoutManager:(NSLayoutManager *)layoutManager; /** @abstract Returns the bounding size for the text view's text. - @param components The TextKit components to calculate the constrained size of the text for. @param constrainedWidth The constraining width to be used during text-sizing. Usually, this value should be the receiver's calculated size. @result A CGSize representing the bounding size for the receiver's text. */ diff --git a/AsyncDisplayKit/TextKit/ASTextKitHelpers.mm b/AsyncDisplayKit/TextKit/ASTextKitComponents.m similarity index 67% rename from AsyncDisplayKit/TextKit/ASTextKitHelpers.mm rename to AsyncDisplayKit/TextKit/ASTextKitComponents.m index 82252b1b7f..4283e3993f 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitHelpers.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitComponents.m @@ -6,7 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "ASTextKitHelpers.h" +#import "ASTextKitComponents.h" @interface ASTextKitComponents () @@ -19,15 +19,25 @@ @implementation ASTextKitComponents -+ (ASTextKitComponents *)componentsWithAttributedSeedString:(NSAttributedString *)attributedSeedString - textContainerSize:(CGSize)textContainerSize ++ (instancetype)componentsWithAttributedSeedString:(NSAttributedString *)attributedSeedString + textContainerSize:(CGSize)textContainerSize { - ASTextKitComponents *components = [[ASTextKitComponents alloc] init]; + NSTextStorage *textStorage = attributedSeedString ? [[NSTextStorage alloc] initWithAttributedString:attributedSeedString] : [[NSTextStorage alloc] init]; - // Create the TextKit component stack with our default configuration. - components.textStorage = (attributedSeedString ? [[NSTextStorage alloc] initWithAttributedString:attributedSeedString] : [[NSTextStorage alloc] init]); + return [self componentsWithTextStorage:textStorage + textContainerSize:textContainerSize + layoutManager:[[NSLayoutManager alloc] init]]; +} - components.layoutManager = [[NSLayoutManager alloc] init]; ++ (instancetype)componentsWithTextStorage:(NSTextStorage *)textStorage + textContainerSize:(CGSize)textContainerSize + layoutManager:(NSLayoutManager *)layoutManager +{ + ASTextKitComponents *components = [[self alloc] init]; + + components.textStorage = textStorage; + + components.layoutManager = layoutManager; [components.textStorage addLayoutManager:components.layoutManager]; components.textContainer = [[NSTextContainer alloc] initWithSize:textContainerSize]; diff --git a/AsyncDisplayKit/TextKit/ASTextKitContext.mm b/AsyncDisplayKit/TextKit/ASTextKitContext.mm index b9925d4f7e..13a4f1ecee 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitContext.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitContext.mm @@ -8,16 +8,15 @@ * */ -#import - #import "ASTextKitContext.h" +#import "ASThread.h" #import "ASLayoutManager.h" @implementation ASTextKitContext { // All TextKit operations (even non-mutative ones) must be executed serially. - std::mutex _textKitMutex; + ASDN::Mutex _textKitMutex; NSLayoutManager *_layoutManager; NSTextStorage *_textStorage; @@ -36,8 +35,8 @@ { if (self = [super init]) { // Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. - static std::mutex __static_mutex; - std::lock_guard l(__static_mutex); + static ASDN::Mutex __staticMutex; + ASDN::MutexLocker l(__staticMutex); // Create the TextKit component stack with our default configuration. if (textStorageCreationBlock) { _textStorage = textStorageCreationBlock(attributedString); @@ -61,11 +60,13 @@ - (CGSize)constrainedSize { + ASDN::MutexLocker l(_textKitMutex); return _textContainer.size; } - (void)setConstrainedSize:(CGSize)constrainedSize { + ASDN::MutexLocker l(_textKitMutex); _textContainer.size = constrainedSize; } @@ -73,7 +74,7 @@ NSTextStorage *, NSTextContainer *))block { - std::lock_guard l(_textKitMutex); + ASDN::MutexLocker l(_textKitMutex); block(_layoutManager, _textStorage, _textContainer); } diff --git a/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.h b/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.h index 9b8ad1ae56..6fe202fa36 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.h +++ b/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.h @@ -76,7 +76,7 @@ ASDISPLAYNODE_EXTERN_C_END - kCTParagraphStyleSpecifierMinimumLineSpacing - kCTParagraphStyleSpecifierLineSpacingAdjustment - kCTParagraphStyleSpecifierLineBoundsOptions - @result An NSParagraphStyle initializd with as many of the paragraph specifiers from `coreTextParagraphStyle` as possible. + @result An NSParagraphStyle initialized with as many of the paragraph specifiers from `coreTextParagraphStyle` as possible. */ + (instancetype)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle; diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm index f881ae5f34..2ff6cf68d3 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm @@ -20,6 +20,10 @@ __weak ASTextKitContext *_context; ASTextKitAttributes _attributes; std::mutex _textKitMutex; + BOOL _measured; + CGFloat _scaleFactor; + NSLayoutManager *_sizingLayoutManager; + NSTextContainer *_sizingTextContainer; } - (instancetype)initWithContext:(ASTextKitContext *)context @@ -76,36 +80,50 @@ - (NSUInteger)lineCountForString:(NSAttributedString *)attributedString { - NSUInteger lineCount = 0; - - static std::mutex __static_mutex; - std::lock_guard l(__static_mutex); - - NSTextStorage *textStorage = _attributes.textStorageCreationBlock ? _attributes.textStorageCreationBlock(attributedString) : [[NSTextStorage alloc] initWithAttributedString:attributedString]; - NSLayoutManager *layoutManager = _attributes.layoutManagerCreationBlock ? _attributes.layoutManagerCreationBlock() : [[ASLayoutManager alloc] init]; - layoutManager.usesFontLeading = NO; - [textStorage addLayoutManager:layoutManager]; - NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:_constrainedSize]; - - textContainer.lineFragmentPadding = 0; - textContainer.lineBreakMode = _attributes.lineBreakMode; - - // use 0 regardless of what is in the attributes so that we get an accurate line count - textContainer.maximumNumberOfLines = 0; - textContainer.exclusionPaths = _attributes.exclusionPaths; - [layoutManager addTextContainer:textContainer]; - - for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [layoutManager numberOfGlyphs] && lineCount <= _attributes.maximumNumberOfLines; lineCount++) { - [layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; - } - - return lineCount; + NSUInteger lineCount = 0; + + static std::mutex __static_mutex; + std::lock_guard l(__static_mutex); + + NSTextStorage *textStorage = _attributes.textStorageCreationBlock ? _attributes.textStorageCreationBlock(attributedString) : [[NSTextStorage alloc] initWithAttributedString:attributedString]; + if (_sizingLayoutManager == nil) { + _sizingLayoutManager = _attributes.layoutManagerCreationBlock ? _attributes.layoutManagerCreationBlock() : [[ASLayoutManager alloc] init]; + _sizingLayoutManager.usesFontLeading = NO; + } + [textStorage addLayoutManager:_sizingLayoutManager]; + if (_sizingTextContainer == nil) { + // make this text container unbounded in height so that the layout manager will compute the total + // number of lines and not stop counting when height runs out. + _sizingTextContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(_constrainedSize.width, FLT_MAX)]; + _sizingTextContainer.lineFragmentPadding = 0; + + // use 0 regardless of what is in the attributes so that we get an accurate line count + _sizingTextContainer.maximumNumberOfLines = 0; + [_sizingLayoutManager addTextContainer:_sizingTextContainer]; + } + + _sizingTextContainer.lineBreakMode = _attributes.lineBreakMode; + _sizingTextContainer.exclusionPaths = _attributes.exclusionPaths; + + + for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [_sizingLayoutManager numberOfGlyphs] && lineCount <= _attributes.maximumNumberOfLines; lineCount++) { + [_sizingLayoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; + } + + [textStorage removeLayoutManager:_sizingLayoutManager]; + return lineCount; } - (CGFloat)scaleFactor { + if (_measured) { + return _scaleFactor; + } + if ([_attributes.pointSizeScaleFactors count] == 0 || isinf(_constrainedSize.width)) { - return 1.0; + _measured = YES; + _scaleFactor = 1.0; + return _scaleFactor; } __block CGFloat adjustedScale = 1.0; @@ -177,7 +195,9 @@ } }]; - return adjustedScale; + _measured = YES; + _scaleFactor = adjustedScale; + return _scaleFactor; } @end diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm index 57ae440fa1..881abb9bf4 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm @@ -22,128 +22,136 @@ static const CGFloat ASTextKitRendererTextCapHeightPadding = 1.3; @implementation ASTextKitRenderer (Tracking) -- (NSArray *)rectsForTextRange:(NSRange)textRange - measureOption:(ASTextKitRendererMeasureOption)measureOption +- (NSArray *)rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption { - __block NSArray *textRects = @[]; + __block NSArray *textRects = nil; [self.context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - NSRange clampedRange = NSIntersectionRange(textRange, NSMakeRange(0, [textStorage length])); - if (clampedRange.location == NSNotFound || clampedRange.length == 0) { - return; - } + textRects = [self unlockedRectsForTextRange:textRange measureOptions:measureOption layoutManager:layoutManager textStorage:textStorage textContainer:textContainer]; + }]; + return textRects; +} - // Used for block measure option - __block CGRect firstRect = CGRectNull; - __block CGRect lastRect = CGRectNull; - __block CGRect blockRect = CGRectNull; - NSMutableArray *mutableTextRects = [NSMutableArray array]; +/** + Helper function that should be called within performBlockWithLockedTextKitComponents: in an already locked state to + prevent a deadlock + */ +- (NSArray *)unlockedRectsForTextRange:(NSRange)textRange measureOptions:(ASTextKitRendererMeasureOption)measureOption layoutManager:(NSLayoutManager *)layoutManager textStorage:(NSTextStorage *)textStorage textContainer:(NSTextContainer *)textContainer +{ + NSRange clampedRange = NSIntersectionRange(textRange, NSMakeRange(0, [textStorage length])); + if (clampedRange.location == NSNotFound || clampedRange.length == 0) { + return @[]; + } - NSString *string = textStorage.string; + // Used for block measure option + __block CGRect firstRect = CGRectNull; + __block CGRect lastRect = CGRectNull; + __block CGRect blockRect = CGRectNull; + NSMutableArray *mutableTextRects = [NSMutableArray array]; - NSRange totalGlyphRange = [layoutManager glyphRangeForCharacterRange:clampedRange actualCharacterRange:NULL]; + NSString *string = textStorage.string; - [layoutManager enumerateLineFragmentsForGlyphRange:totalGlyphRange usingBlock:^(CGRect rect, - CGRect usedRect, - NSTextContainer *innerTextContainer, - NSRange glyphRange, - BOOL *stop) { + NSRange totalGlyphRange = [layoutManager glyphRangeForCharacterRange:clampedRange actualCharacterRange:NULL]; - CGRect lineRect = CGRectNull; - // If we're empty, don't bother looping through glyphs, use the default. - if (CGRectIsEmpty(usedRect)) { - lineRect = usedRect; - } else { - // TextKit's bounding rect computations are just a touch off, so we actually - // compose the rects by hand from the center of the given TextKit bounds and - // imposing the font attributes returned by the glyph's font. - NSRange lineGlyphRange = NSIntersectionRange(totalGlyphRange, glyphRange); - for (NSUInteger i = lineGlyphRange.location; i < NSMaxRange(lineGlyphRange) && i < string.length; i++) { - // We grab the properly sized rect for the glyph - CGRect properGlyphRect = [self _internalRectForGlyphAtIndex:i - measureOption:measureOption - layoutManager:layoutManager - textContainer:textContainer - textStorage:textStorage]; + [layoutManager enumerateLineFragmentsForGlyphRange:totalGlyphRange usingBlock:^(CGRect rect, + CGRect usedRect, + NSTextContainer *innerTextContainer, + NSRange glyphRange, + BOOL *stop) { - // Don't count empty glyphs towards our line rect. - if (!CGRectIsEmpty(properGlyphRect)) { - lineRect = CGRectIsNull(lineRect) ? properGlyphRect - : CGRectUnion(lineRect, properGlyphRect); - } + CGRect lineRect = CGRectNull; + // If we're empty, don't bother looping through glyphs, use the default. + if (CGRectIsEmpty(usedRect)) { + lineRect = usedRect; + } else { + // TextKit's bounding rect computations are just a touch off, so we actually + // compose the rects by hand from the center of the given TextKit bounds and + // imposing the font attributes returned by the glyph's font. + NSRange lineGlyphRange = NSIntersectionRange(totalGlyphRange, glyphRange); + for (NSUInteger i = lineGlyphRange.location; i < NSMaxRange(lineGlyphRange) && i < string.length; i++) { + // We grab the properly sized rect for the glyph + CGRect properGlyphRect = [self _internalRectForGlyphAtIndex:i + measureOption:measureOption + layoutManager:layoutManager + textContainer:textContainer + textStorage:textStorage]; + + // Don't count empty glyphs towards our line rect. + if (!CGRectIsEmpty(properGlyphRect)) { + lineRect = CGRectIsNull(lineRect) ? properGlyphRect + : CGRectUnion(lineRect, properGlyphRect); } } + } - if (!CGRectIsNull(lineRect)) { - if (measureOption == ASTextKitRendererMeasureOptionBlock) { - // For the block measurement option we store the first & last rect as - // special cases, then merge everything else into a single block rect - if (CGRectIsNull(firstRect)) { - // We don't have a firstRect, so we must be on the first line. - firstRect = lineRect; - } else if(CGRectIsNull(lastRect)) { - // We don't have a lastRect, but we do have a firstRect, so we must - // be on the second line. No need to merge in the blockRect just yet - lastRect = lineRect; - } else if(CGRectIsNull(blockRect)) { - // We have both a first and last rect, so we must be on the third line - // we don't have any blockRect to merge it into, so we just set it - // directly. - blockRect = lastRect; - lastRect = lineRect; - } else { - // Everything is already set, so we just merge this line into the - // block. - blockRect = CGRectUnion(blockRect, lastRect); - lastRect = lineRect; - } + if (!CGRectIsNull(lineRect)) { + if (measureOption == ASTextKitRendererMeasureOptionBlock) { + // For the block measurement option we store the first & last rect as + // special cases, then merge everything else into a single block rect + if (CGRectIsNull(firstRect)) { + // We don't have a firstRect, so we must be on the first line. + firstRect = lineRect; + } else if(CGRectIsNull(lastRect)) { + // We don't have a lastRect, but we do have a firstRect, so we must + // be on the second line. No need to merge in the blockRect just yet + lastRect = lineRect; + } else if(CGRectIsNull(blockRect)) { + // We have both a first and last rect, so we must be on the third line + // we don't have any blockRect to merge it into, so we just set it + // directly. + blockRect = lastRect; + lastRect = lineRect; } else { - // If the block option isn't being used then each line is being treated - // individually. - [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:lineRect]]]; + // Everything is already set, so we just merge this line into the + // block. + blockRect = CGRectUnion(blockRect, lastRect); + lastRect = lineRect; } - } - }]; - - if (measureOption == ASTextKitRendererMeasureOptionBlock) { - // Block measure option is handled differently with just 3 vars for the entire range. - if (!CGRectIsNull(firstRect)) { - if (!CGRectIsNull(blockRect)) { - CGFloat rightEdge = MAX(CGRectGetMaxX(blockRect), CGRectGetMaxX(lastRect)); - if (rightEdge > CGRectGetMaxX(firstRect)) { - // Force the right side of the first rect to properly align with the - // right side of the rightmost of the block and last rect - firstRect.size.width += rightEdge - CGRectGetMaxX(firstRect); - } - - // Force the left side of the block rect to properly align with the - // left side of the leftmost of the first and last rect - blockRect.origin.x = MIN(CGRectGetMinX(firstRect), CGRectGetMinX(lastRect)); - // Force the right side of the block rect to properly align with the - // right side of the rightmost of the first and last rect - blockRect.size.width += MAX(CGRectGetMaxX(firstRect), CGRectGetMaxX(lastRect)) - CGRectGetMaxX(blockRect); - } - if (!CGRectIsNull(lastRect)) { - // Force the left edge of the last rect to properly align with the - // left side of the leftmost of the first and block rect, if necessary. - CGFloat leftEdge = MIN(CGRectGetMinX(blockRect), CGRectGetMinX(firstRect)); - CGFloat lastRectNudgeAmount = MAX(CGRectGetMinX(lastRect) - leftEdge, 0); - lastRect.origin.x = MIN(leftEdge, CGRectGetMinX(lastRect)); - lastRect.size.width += lastRectNudgeAmount; - } - - [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:firstRect]]]; - } - if (!CGRectIsNull(blockRect)) { - [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:blockRect]]]; - } - if (!CGRectIsNull(lastRect)) { - [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:lastRect]]]; + } else { + // If the block option isn't being used then each line is being treated + // individually. + [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:lineRect]]]; } } - textRects = mutableTextRects; }]; - return textRects; + if (measureOption == ASTextKitRendererMeasureOptionBlock) { + // Block measure option is handled differently with just 3 vars for the entire range. + if (!CGRectIsNull(firstRect)) { + if (!CGRectIsNull(blockRect)) { + CGFloat rightEdge = MAX(CGRectGetMaxX(blockRect), CGRectGetMaxX(lastRect)); + if (rightEdge > CGRectGetMaxX(firstRect)) { + // Force the right side of the first rect to properly align with the + // right side of the rightmost of the block and last rect + firstRect.size.width += rightEdge - CGRectGetMaxX(firstRect); + } + + // Force the left side of the block rect to properly align with the + // left side of the leftmost of the first and last rect + blockRect.origin.x = MIN(CGRectGetMinX(firstRect), CGRectGetMinX(lastRect)); + // Force the right side of the block rect to properly align with the + // right side of the rightmost of the first and last rect + blockRect.size.width += MAX(CGRectGetMaxX(firstRect), CGRectGetMaxX(lastRect)) - CGRectGetMaxX(blockRect); + } + if (!CGRectIsNull(lastRect)) { + // Force the left edge of the last rect to properly align with the + // left side of the leftmost of the first and block rect, if necessary. + CGFloat leftEdge = MIN(CGRectGetMinX(blockRect), CGRectGetMinX(firstRect)); + CGFloat lastRectNudgeAmount = MAX(CGRectGetMinX(lastRect) - leftEdge, 0); + lastRect.origin.x = MIN(leftEdge, CGRectGetMinX(lastRect)); + lastRect.size.width += lastRectNudgeAmount; + } + + [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:firstRect]]]; + } + if (!CGRectIsNull(blockRect)) { + [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:blockRect]]]; + } + if (!CGRectIsNull(lastRect)) { + [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:lastRect]]]; + } + } + + return [mutableTextRects copy]; } - (NSUInteger)nearestTextIndexAtPosition:(CGPoint)position @@ -342,7 +350,8 @@ static const CGFloat ASTextKitRendererTextCapHeightPadding = 1.3; } // Take everything after our final character as trailing space. - NSArray *finalRects = [self rectsForTextRange:NSMakeRange([textStorage length] - 1, 1) measureOption:ASTextKitRendererMeasureOptionLineHeight]; + NSRange textRange = NSMakeRange([textStorage length] - 1, 1); + NSArray *finalRects = [self unlockedRectsForTextRange:textRange measureOptions:ASTextKitRendererMeasureOptionLineHeight layoutManager:layoutManager textStorage:textStorage textContainer:textContainer]; CGRect finalGlyphRect = [[finalRects lastObject] CGRectValue]; CGPoint origin = CGPointMake(CGRectGetMaxX(finalGlyphRect), CGRectGetMinY(finalGlyphRect)); CGSize size = CGSizeMake(calculatedSize.width - origin.x, calculatedSize.height - origin.y); diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.h b/AsyncDisplayKit/TextKit/ASTextKitRenderer.h index 1889131f52..e57c5dc174 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.h +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.h @@ -58,7 +58,7 @@ @property (nonatomic, assign, readonly) CGFloat currentScaleFactor; #pragma mark - Drawing -/* +/** Draw the renderer's text content into the bounds provided. @param bounds The rect in which to draw the contents of the renderer. @@ -67,20 +67,20 @@ #pragma mark - Layout -/* +/** Returns the computed size of the renderer given the constrained size and other parameters in the initializer. */ - (CGSize)size; #pragma mark - Text Ranges -/* +/** The character range from the original attributedString that is displayed by the renderer given the parameters in the initializer. */ - (std::vector)visibleRanges; -/* +/** The number of lines shown in the string. */ - (NSUInteger)lineCount; diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm index 5ad0e302d3..bc7d2c1069 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm @@ -49,9 +49,6 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() _constrainedSize = constrainedSize; _attributes = attributes; _sizeIsCalculated = NO; - if ([attributes.pointSizeScaleFactors count] > 0) { - _currentScaleFactor = attributes.currentScaleFactor; - } } return self; } @@ -141,31 +138,48 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() - (void)_calculateSize { [self truncater]; - if ([_attributes.pointSizeScaleFactors count] > 0) { + // if we have no scale factors or an unconstrained width, there is no reason to try to adjust the font size + if (isinf(_constrainedSize.width) == NO && [_attributes.pointSizeScaleFactors count] > 0) { _currentScaleFactor = [[self fontSizeAdjuster] scaleFactor]; } - + // Force glyph generation and layout, which may not have happened yet (and isn't triggered by // -usedRectForTextContainer:). + __block NSTextStorage *scaledTextStorage = nil; + BOOL isScaled = [self isScaled]; [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + if (isScaled) { + NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; + [ASTextKitFontSizeAdjuster adjustFontSizeForAttributeString:scaledString withScaleFactor:_currentScaleFactor]; + scaledTextStorage = [[NSTextStorage alloc] initWithAttributedString:scaledString]; + + [textStorage removeLayoutManager:layoutManager]; + [scaledTextStorage addLayoutManager:layoutManager]; + } [layoutManager ensureLayoutForTextContainer:textContainer]; }]; - + CGRect constrainedRect = {CGPointZero, _constrainedSize}; __block CGRect boundingRect; [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { boundingRect = [layoutManager usedRectForTextContainer:textContainer]; + if (isScaled) { + // put the non-scaled version back + [scaledTextStorage removeLayoutManager:layoutManager]; + [textStorage addLayoutManager:layoutManager]; + } }]; - + // TextKit often returns incorrect glyph bounding rects in the horizontal direction, so we clip to our bounding rect // to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect. boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size}); CGSize boundingSize = [_shadower outsetSizeWithInsetSize:boundingRect.size]; _calculatedSize = CGSizeMake(boundingSize.width, boundingSize.height); - - if (_currentScaleFactor > 0.0 && _currentScaleFactor < 1.0) { - _calculatedSize.height = ceilf(_calculatedSize.height * _currentScaleFactor); - } +} + +- (BOOL)isScaled +{ + return (self.currentScaleFactor > 0 && self.currentScaleFactor < 1.0); } #pragma mark - Drawing @@ -174,6 +188,12 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() { // We add an assertion so we can track the rare conditions where a graphics context is not present ASDisplayNodeAssertNotNil(context, @"This is no good without a context."); + + // This renderer may not be the one that did the sizing. If that is the case its truncation and currentScaleFactor may not have been evaluated. + // If there's any possibility we need to truncate or scale (e.g. width is not infinite, perform the size calculation. + if (_sizeIsCalculated == NO && isinf(_constrainedSize.width) == NO) { + [self _calculateSize]; + } CGRect shadowInsetBounds = [[self shadower] insetRectWithConstrainedRect:bounds]; @@ -186,7 +206,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { NSTextStorage *scaledTextStorage = nil; - BOOL isScaled = (self.currentScaleFactor > 0 && self.currentScaleFactor < 1.0); + BOOL isScaled = [self isScaled]; if (isScaled) { // if we are going to scale the text, swap out the non-scaled text for the scaled version. diff --git a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm b/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm index 1ea45e2b91..d48013a772 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm @@ -62,7 +62,7 @@ // We assume LTR so long as the writing direction is not BOOL rtlWritingDirection = paragraphStyle ? paragraphStyle.baseWritingDirection == NSWritingDirectionRightToLeft : NO; - // We only want to treat the trunction rect as left-aligned in the case that we are right-aligned and our writing + // We only want to treat the truncation rect as left-aligned in the case that we are right-aligned and our writing // direction is RTL. BOOL leftAligned = CGRectGetMinX(lastLineRect) == CGRectGetMinX(lastLineUsedRect) || !rtlWritingDirection; diff --git a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m b/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m index ffd9655a91..f0ae6eea7f 100644 --- a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m +++ b/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m @@ -68,7 +68,7 @@ { // If it's a space character and we have custom word kerning, use the whitespace action control character. if ([layoutManager.textStorage.string characterAtIndex:characterIndex] == ' ') - return NSControlCharacterWhitespaceAction; + return NSControlCharacterActionWhitespace; return defaultAction; } diff --git a/AsyncDisplayKit/_ASTransitionContext.h b/AsyncDisplayKit/_ASTransitionContext.h index 9411d76ac1..c9d4cd7ff9 100644 --- a/AsyncDisplayKit/_ASTransitionContext.h +++ b/AsyncDisplayKit/_ASTransitionContext.h @@ -13,7 +13,7 @@ @class ASLayout; @class _ASTransitionContext; -@protocol _ASTransitionContextDelegate +@protocol _ASTransitionContextLayoutDelegate - (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context; @@ -23,6 +23,10 @@ - (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key; - (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key; +@end + +@protocol _ASTransitionContextCompletionDelegate + - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete; @end @@ -31,6 +35,8 @@ @property (assign, readonly, nonatomic, getter=isAnimated) BOOL animated; -- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate; +- (instancetype)initWithAnimation:(BOOL)animated + layoutDelegate:(id<_ASTransitionContextLayoutDelegate>)layoutDelegate + completionDelegate:(id<_ASTransitionContextCompletionDelegate>)completionDelegate; @end diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m index 8c69f194fa..2474f3f395 100644 --- a/AsyncDisplayKit/_ASTransitionContext.m +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -16,18 +16,22 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi @interface _ASTransitionContext () -@property (weak, nonatomic) id<_ASTransitionContextDelegate> delegate; +@property (weak, nonatomic) id<_ASTransitionContextLayoutDelegate> layoutDelegate; +@property (weak, nonatomic) id<_ASTransitionContextCompletionDelegate> completionDelegate; @end @implementation _ASTransitionContext -- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate +- (instancetype)initWithAnimation:(BOOL)animated + layoutDelegate:(id<_ASTransitionContextLayoutDelegate>)layoutDelegate + completionDelegate:(id<_ASTransitionContextCompletionDelegate>)completionDelegate { self = [super init]; if (self) { _animated = animated; - _delegate = delegate; + _layoutDelegate = layoutDelegate; + _completionDelegate = completionDelegate; } return self; } @@ -36,17 +40,17 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi - (ASLayout *)layoutForKey:(NSString *)key { - return [_delegate transitionContext:self layoutForKey:key]; + return [_layoutDelegate transitionContext:self layoutForKey:key]; } - (ASSizeRange)constrainedSizeForKey:(NSString *)key { - return [_delegate transitionContext:self constrainedSizeForKey:key]; + return [_layoutDelegate transitionContext:self constrainedSizeForKey:key]; } - (CGRect)initialFrameForNode:(ASDisplayNode *)node { - for (ASDisplayNode *subnode in [_delegate currentSubnodesWithTransitionContext:self]) { + for (ASDisplayNode *subnode in [_layoutDelegate currentSubnodesWithTransitionContext:self]) { if (node == subnode) { return node.frame; } @@ -75,17 +79,17 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi - (NSArray *)insertedSubnodes { - return [_delegate insertedSubnodesWithTransitionContext:self]; + return [_layoutDelegate insertedSubnodesWithTransitionContext:self]; } - (NSArray *)removedSubnodes { - return [_delegate removedSubnodesWithTransitionContext:self]; + return [_layoutDelegate removedSubnodesWithTransitionContext:self]; } - (void)completeTransition:(BOOL)didComplete { - [_delegate transitionContext:self didComplete:didComplete]; + [_completionDelegate transitionContext:self didComplete:didComplete]; } @end diff --git a/AsyncDisplayKitTestHost/Info.plist b/AsyncDisplayKitTestHost/Info.plist index 074288945b..ed1c9acf9b 100644 --- a/AsyncDisplayKitTestHost/Info.plist +++ b/AsyncDisplayKitTestHost/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m index 9def4240a1..b3c772ae8a 100644 --- a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m +++ b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m @@ -1,5 +1,5 @@ // -// ASZBasicImageDownloaderTests.m +// ASBasicImageDownloaderTests.m // AsyncDisplayKit // // Created by Victor Mayorov on 10/06/15. @@ -10,7 +10,6 @@ #import -// Z in the name to delay running until after the test instance is operating normally. @interface ASBasicImageDownloaderTests : XCTestCase @end @@ -19,30 +18,30 @@ - (void)testAsynchronouslyDownloadTheSameURLTwice { - ASBasicImageDownloader *downloader = [ASBasicImageDownloader sharedImageDownloader]; - - NSURL *URL = [NSURL URLWithString:@"http://wrongPath/wrongResource.png"]; - - __block BOOL firstDone = NO; - - [downloader downloadImageWithURL:URL - callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) - downloadProgressBlock:nil - completion:^(CGImageRef image, NSError *error) { - firstDone = YES; - }]; - - __block BOOL secondDone = NO; - - [downloader downloadImageWithURL:URL - callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) - downloadProgressBlock:nil - completion:^(CGImageRef image, NSError *error) { - secondDone = YES; - }]; - - sleep(3); - XCTAssert(firstDone && secondDone, @"Not all ASBasicImageDownloader completion handlers have been called after 3 seconds"); + XCTestExpectation *firstExpectation = [self expectationWithDescription:@"First ASBasicImageDownloader completion handler should be called within 3 seconds"]; + XCTestExpectation *secondExpectation = [self expectationWithDescription:@"Second ASBasicImageDownloader completion handler should be called within 3 seconds"]; + + ASBasicImageDownloader *downloader = [ASBasicImageDownloader sharedImageDownloader]; + NSURL *URL = [NSURL URLWithString:@"http://wrongPath/wrongResource.png"]; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [downloader downloadImageWithURL:URL + callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) + downloadProgressBlock:nil + completion:^(CGImageRef image, NSError *error) { + [firstExpectation fulfill]; + }]; + + [downloader downloadImageWithURL:URL + callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) + downloadProgressBlock:nil + completion:^(CGImageRef image, NSError *error) { + [secondExpectation fulfill]; + }]; +#pragma clang diagnostic pop + + [self waitForExpectationsWithTimeout:3 handler:nil]; } @end diff --git a/AsyncDisplayKitTests/ASBatchFetchingTests.m b/AsyncDisplayKitTests/ASBatchFetchingTests.m index 324f113ec1..a69e7b1f00 100644 --- a/AsyncDisplayKitTests/ASBatchFetchingTests.m +++ b/AsyncDisplayKitTests/ASBatchFetchingTests.m @@ -28,34 +28,34 @@ - (void)testBatchNullState { ASBatchContext *context = [[ASBatchContext alloc] init]; - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, CGRectZero, CGSizeZero, CGPointZero, 0.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, CGRectZero, CGSizeZero, CGPointZero, 0.0); XCTAssert(shouldFetch == NO, @"Should not fetch in the null state"); } - (void)testBatchAlreadyFetching { ASBatchContext *context = [[ASBatchContext alloc] init]; [context beginBatchFetching]; - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0); XCTAssert(shouldFetch == NO, @"Should not fetch when context is already fetching"); } - (void)testUnsupportedScrollDirections { ASBatchContext *context = [[ASBatchContext alloc] init]; BOOL fetchRight = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0); - XCTAssert(fetchRight == NO, @"Should not fetch for scrolling right"); + XCTAssert(fetchRight == YES, @"Should fetch for scrolling right"); BOOL fetchDown = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0); - XCTAssert(fetchDown == NO, @"Should not fetch for scrolling down"); + XCTAssert(fetchDown == YES, @"Should fetch for scrolling down"); BOOL fetchUp = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0); - XCTAssert(fetchUp == YES, @"Should fetch for scrolling up"); + XCTAssert(fetchUp == NO, @"Should not fetch for scrolling up"); BOOL fetchLeft = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0); - XCTAssert(fetchLeft == YES, @"Should fetch for scrolling left"); + XCTAssert(fetchLeft == NO, @"Should not fetch for scrolling left"); } - (void)testVerticalScrollToExactLeading { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // scroll to 1-screen top offset, height is 1 screen, so bottom is 1 screen away from end of content - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 1.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 1.0), 1.0); XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling to exactly 1 leading screen away"); } @@ -63,7 +63,7 @@ CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, scroll only 1/2 of one screen - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 0.5), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 0.5), 1.0); XCTAssert(shouldFetch == NO, @"Fetch should not begin when vertically scrolling less than the leading distance away"); } @@ -71,7 +71,7 @@ CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, top offset to 3-screens, height 1 screen, so its 1 screen past the leading - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 3.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 3.0), 1.0); XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size"); } @@ -79,7 +79,7 @@ CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // scroll to 1-screen left offset, width is 1 screen, so right is 1 screen away from end of content - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 1.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 1.0), 1.0); XCTAssert(shouldFetch == YES, @"Fetch should begin when horizontally scrolling to exactly 1 leading screen away"); } @@ -87,7 +87,7 @@ CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, scroll only 1/2 of one screen - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 0.5), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 0.5), 1.0); XCTAssert(shouldFetch == NO, @"Fetch should not begin when horizontally scrolling less than the leading distance away"); } @@ -95,7 +95,7 @@ CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, left offset to 3-screens, width 1 screen, so its 1 screen past the leading - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 3.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 3.0), 1.0); XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size"); } @@ -103,7 +103,7 @@ CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // when the content size is < screen size, the target offset will always be 0 - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 0.5), VERTICAL_OFFSET(0.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 0.5), VERTICAL_OFFSET(0.0), 1.0); XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree"); } @@ -111,7 +111,7 @@ CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // when the content size is < screen size, the target offset will always be 0 - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 0.5), HORIZONTAL_OFFSET(0.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 0.5), HORIZONTAL_OFFSET(0.0), 1.0); XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree"); } diff --git a/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm index 10c129f10c..bb38be0d18 100644 --- a/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm +++ b/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm @@ -13,7 +13,6 @@ #import "ASBackgroundLayoutSpec.h" #import "ASCenterLayoutSpec.h" #import "ASStackLayoutSpec.h" -#import "ASLayoutOptions.h" static const ASSizeRange kSize = {{100, 120}, {320, 160}}; @@ -38,10 +37,14 @@ static const ASSizeRange kSize = {{100, 120}, {320, 160}}; - (void)testWithSizingOptions { - [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionDefault]; - [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionMinimumX]; - [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionMinimumY]; - [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionMinimumXY]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone + sizingOptions:ASCenterLayoutSpecSizingOptionDefault]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone + sizingOptions:ASCenterLayoutSpecSizingOptionMinimumX]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone + sizingOptions:ASCenterLayoutSpecSizingOptionMinimumY]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone + sizingOptions:ASCenterLayoutSpecSizingOptionMinimumXY]; } - (void)testWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)options @@ -51,14 +54,7 @@ static const ASSizeRange kSize = {{100, 120}, {320, 160}}; ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor]); foregroundNode.staticSize = {70, 100}; - ASLayoutSpec *layoutSpec = - [ASBackgroundLayoutSpec - backgroundLayoutSpecWithChild: - [ASCenterLayoutSpec - centerLayoutSpecWithCenteringOptions:options - sizingOptions:sizingOptions - child:foregroundNode] - background:backgroundNode]; + ASLayoutSpec *layoutSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:[ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:options sizingOptions:sizingOptions child:foregroundNode]background:backgroundNode]; [self testLayoutSpec:layoutSpec sizeRange:kSize @@ -97,14 +93,7 @@ static NSString *suffixForCenteringOptions(ASCenterLayoutSpecCenteringOptions ce foregroundNode.staticSize = {10, 10}; foregroundNode.flexGrow = YES; - ASCenterLayoutSpec *layoutSpec = - [ASCenterLayoutSpec - centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringNone - sizingOptions:{} - child: - [ASBackgroundLayoutSpec - backgroundLayoutSpecWithChild:[ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart children:@[foregroundNode]] - background:backgroundNode]]; + ASCenterLayoutSpec *layoutSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:{} child:[ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:[ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart children:@[foregroundNode]] background:backgroundNode]]; [self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier:nil]; } diff --git a/AsyncDisplayKitTests/ASControlNodeTests.m b/AsyncDisplayKitTests/ASControlNodeTests.m index 969947c3ad..5b094ef0dd 100644 --- a/AsyncDisplayKitTests/ASControlNodeTests.m +++ b/AsyncDisplayKitTests/ASControlNodeTests.m @@ -106,6 +106,76 @@ XCTAssert(controller.hits == 1, @"Controller did not receive the action event"); } +- (void)testRemoveWithoutTargetRemovesTargetlessAction { + ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init]; + ASControlNode *node = [[ASControlNode alloc] init]; + [node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [node removeTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [controller.view addSubview:node.view]; + [node sendActionsForControlEvents:EVENT withEvent:nil]; + XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events"); +} + +- (void)testRemoveWithTarget { + ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init]; + ASControlNode *node = [[ASControlNode alloc] init]; + [node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [node removeTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [controller.view addSubview:node.view]; + [node sendActionsForControlEvents:EVENT withEvent:nil]; + XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events"); +} + +- (void)testRemoveWithTargetRemovesAction { + ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init]; + ASControlNode *node = [[ASControlNode alloc] init]; + [node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [node removeTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [controller.view addSubview:node.view]; + [node sendActionsForControlEvents:EVENT withEvent:nil]; + XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events"); +} + +- (void)testRemoveWithoutTargetRemovesTargetedAction { + ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init]; + ASControlNode *node = [[ASControlNode alloc] init]; + [node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [node removeTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [controller.view addSubview:node.view]; + [node sendActionsForControlEvents:EVENT withEvent:nil]; + XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events"); +} + +- (void)testDuplicateEntriesWithoutTarget { + ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init]; + ASControlNode *node = [[ASControlNode alloc] init]; + [node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [controller.view addSubview:node.view]; + [node sendActionsForControlEvents:EVENT withEvent:nil]; + XCTAssertEqual(controller.hits, 1, @"Controller did not receive exactly one action event"); +} + +- (void)testDuplicateEntriesWithTarget { + ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init]; + ASControlNode *node = [[ASControlNode alloc] init]; + [node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [controller.view addSubview:node.view]; + [node sendActionsForControlEvents:EVENT withEvent:nil]; + XCTAssertEqual(controller.hits, 1, @"Controller did not receive exactly one action event"); +} + +- (void)testDuplicateEntriesWithAndWithoutTarget { + ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init]; + ASControlNode *node = [[ASControlNode alloc] init]; + [node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT]; + [controller.view addSubview:node.view]; + [node sendActionsForControlEvents:EVENT withEvent:nil]; + XCTAssertEqual(controller.hits, 2, @"Controller did not receive exactly two action events"); +} + - (void)testDeeperHierarchyWithoutTarget { ASActionController *controller = [[ASActionController alloc] init]; UIView *view = [[UIView alloc] init]; diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 455591cd1b..36b01040bf 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -75,7 +75,6 @@ for (ASDisplayNode *n in @[ nodes ]) {\ @interface ASDisplayNode (HackForTests) -+ (dispatch_queue_t)asyncSizingQueue; - (id)initWithViewClass:(Class)viewClass; - (id)initWithLayerClass:(Class)layerClass; @@ -278,7 +277,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\ NSString *targetName = isLayerBacked ? @"layer" : @"view"; NSString *hasLoadedView = node.nodeLoaded ? @"with view" : [NSString stringWithFormat:@"after loading %@", targetName]; - id rgbBlackCGColorIdPtr = (id)[UIColor colorWithRed:0 green:0 blue:0 alpha:1].CGColor; +// id rgbBlackCGColorIdPtr = (id)[UIColor blackColor].CGColor; XCTAssertEqual((id)nil, node.contents, @"default contents broken %@", hasLoadedView); XCTAssertEqual(NO, node.clipsToBounds, @"default clipsToBounds broken %@", hasLoadedView); @@ -298,16 +297,27 @@ for (ASDisplayNode *n in @[ nodes ]) {\ XCTAssertTrue(CATransform3DEqualToTransform(CATransform3DIdentity, node.subnodeTransform), @"default subnodeTransform broken %@", hasLoadedView); XCTAssertEqual((id)nil, node.backgroundColor, @"default backgroundColor broken %@", hasLoadedView); XCTAssertEqual(UIViewContentModeScaleToFill, node.contentMode, @"default contentMode broken %@", hasLoadedView); - XCTAssertEqualObjects(rgbBlackCGColorIdPtr, (id)node.shadowColor, @"default shadowColor broken %@", hasLoadedView); +// XCTAssertEqualObjects(rgbBlackCGColorIdPtr, (id)node.shadowColor, @"default shadowColor broken %@", hasLoadedView); XCTAssertEqual(0.0f, node.shadowOpacity, @"default shadowOpacity broken %@", hasLoadedView); XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(0, -3), node.shadowOffset), @"default shadowOffset broken %@", hasLoadedView); XCTAssertEqual(3.f, node.shadowRadius, @"default shadowRadius broken %@", hasLoadedView); XCTAssertEqual(0.0f, node.borderWidth, @"default borderWidth broken %@", hasLoadedView); - XCTAssertEqualObjects(rgbBlackCGColorIdPtr, (id)node.borderColor, @"default borderColor broken %@", hasLoadedView); +// XCTAssertEqualObjects(rgbBlackCGColorIdPtr, (id)node.borderColor, @"default borderColor broken %@", hasLoadedView); XCTAssertEqual(NO, node.displaySuspended, @"default displaySuspended broken %@", hasLoadedView); XCTAssertEqual(YES, node.displaysAsynchronously, @"default displaysAsynchronously broken %@", hasLoadedView); XCTAssertEqual(NO, node.asyncdisplaykit_asyncTransactionContainer, @"default asyncdisplaykit_asyncTransactionContainer broken %@", hasLoadedView); XCTAssertEqualObjects(nil, node.name, @"default name broken %@", hasLoadedView); + + XCTAssertEqual(NO, node.isAccessibilityElement, @"default isAccessibilityElement is broken %@", hasLoadedView); + XCTAssertEqual((id)nil, node.accessibilityLabel, @"default accessibilityLabel is broken %@", hasLoadedView); + XCTAssertEqual((id)nil, node.accessibilityHint, @"default accessibilityHint is broken %@", hasLoadedView); + XCTAssertEqual((id)nil, node.accessibilityValue, @"default accessibilityValue is broken %@", hasLoadedView); + XCTAssertEqual(UIAccessibilityTraitNone, node.accessibilityTraits, @"default accessibilityTraits is broken %@", hasLoadedView); + XCTAssertTrue(CGRectEqualToRect(CGRectZero, node.accessibilityFrame), @"default accessibilityFrame is broken %@", hasLoadedView); + XCTAssertEqual((id)nil, node.accessibilityLanguage, @"default accessibilityLanguage is broken %@", hasLoadedView); + XCTAssertEqual(NO, node.accessibilityElementsHidden, @"default accessibilityElementsHidden is broken %@", hasLoadedView); + XCTAssertEqual(NO, node.accessibilityViewIsModal, @"default accessibilityViewIsModal is broken %@", hasLoadedView); + XCTAssertEqual(NO, node.shouldGroupAccessibilityChildren, @"default shouldGroupAccessibilityChildren is broken %@", hasLoadedView); if (!isLayerBacked) { XCTAssertEqual(YES, node.userInteractionEnabled, @"default userInteractionEnabled broken %@", hasLoadedView); @@ -318,19 +328,6 @@ for (ASDisplayNode *n in @[ nodes ]) {\ XCTAssertEqual(NO, node.userInteractionEnabled, @"layer-backed nodes do not support userInteractionEnabled %@", hasLoadedView); XCTAssertEqual(NO, node.exclusiveTouch, @"layer-backed nodes do not support exclusiveTouch %@", hasLoadedView); } - - if (!isLayerBacked) { - XCTAssertEqual(NO, node.isAccessibilityElement, @"default isAccessibilityElement is broken %@", hasLoadedView); - XCTAssertEqual((id)nil, node.accessibilityLabel, @"default accessibilityLabel is broken %@", hasLoadedView); - XCTAssertEqual((id)nil, node.accessibilityHint, @"default accessibilityHint is broken %@", hasLoadedView); - XCTAssertEqual((id)nil, node.accessibilityValue, @"default accessibilityValue is broken %@", hasLoadedView); - XCTAssertEqual(UIAccessibilityTraitNone, node.accessibilityTraits, @"default accessibilityTraits is broken %@", hasLoadedView); - XCTAssertTrue(CGRectEqualToRect(CGRectZero, node.accessibilityFrame), @"default accessibilityFrame is broken %@", hasLoadedView); - XCTAssertEqual((id)nil, node.accessibilityLanguage, @"default accessibilityLanguage is broken %@", hasLoadedView); - XCTAssertEqual(NO, node.accessibilityElementsHidden, @"default accessibilityElementsHidden is broken %@", hasLoadedView); - XCTAssertEqual(NO, node.accessibilityViewIsModal, @"default accessibilityViewIsModal is broken %@", hasLoadedView); - XCTAssertEqual(NO, node.shouldGroupAccessibilityChildren, @"default shouldGroupAccessibilityChildren is broken %@", hasLoadedView); - } } - (void)checkDefaultPropertyValuesWithLayerBacking:(BOOL)isLayerBacked @@ -406,20 +403,25 @@ for (ASDisplayNode *n in @[ nodes ]) {\ XCTAssertEqual(NO, node.userInteractionEnabled, @"userInteractionEnabled broken %@", hasLoadedView); XCTAssertEqual((BOOL)!isLayerBacked, node.exclusiveTouch, @"exclusiveTouch broken %@", hasLoadedView); XCTAssertEqualObjects(@"quack like a duck", node.name, @"name broken %@", hasLoadedView); + + XCTAssertEqual(YES, node.isAccessibilityElement, @"accessibilityElement broken %@", hasLoadedView); + XCTAssertEqualObjects(@"Ship love", node.accessibilityLabel, @"accessibilityLabel broken %@", hasLoadedView); + XCTAssertEqualObjects(@"Awesome things will happen", node.accessibilityHint, @"accessibilityHint broken %@", hasLoadedView); + XCTAssertEqualObjects(@"1 of 2", node.accessibilityValue, @"accessibilityValue broken %@", hasLoadedView); + XCTAssertEqual(UIAccessibilityTraitSelected | UIAccessibilityTraitButton, node.accessibilityTraits, @"accessibilityTraits broken %@", hasLoadedView); + XCTAssertTrue(CGRectEqualToRect(CGRectMake(1, 2, 3, 4), node.accessibilityFrame), @"accessibilityFrame broken %@", hasLoadedView); + XCTAssertEqualObjects(@"mas", node.accessibilityLanguage, @"accessibilityLanguage broken %@", hasLoadedView); + XCTAssertEqual(YES, node.accessibilityElementsHidden, @"accessibilityElementsHidden broken %@", hasLoadedView); + XCTAssertEqual(YES, node.accessibilityViewIsModal, @"accessibilityViewIsModal broken %@", hasLoadedView); + XCTAssertEqual(YES, node.shouldGroupAccessibilityChildren, @"shouldGroupAccessibilityChildren broken %@", hasLoadedView); + XCTAssertEqual(UIAccessibilityNavigationStyleSeparate, node.accessibilityNavigationStyle, @"accessibilityNavigationStyle broken %@", hasLoadedView); + XCTAssertTrue(CGPointEqualToPoint(CGPointMake(1.0, 1.0), node.accessibilityActivationPoint), @"accessibilityActivationPoint broken %@", hasLoadedView); + XCTAssertNotNil(node.accessibilityPath, @"accessibilityPath broken %@", hasLoadedView); + if (!isLayerBacked) { XCTAssertEqual(UIViewAutoresizingFlexibleLeftMargin, node.autoresizingMask, @"autoresizingMask %@", hasLoadedView); XCTAssertEqual(NO, node.autoresizesSubviews, @"autoresizesSubviews broken %@", hasLoadedView); - XCTAssertEqual(YES, node.isAccessibilityElement, @"accessibilityElement broken %@", hasLoadedView); - XCTAssertEqualObjects(@"Ship love", node.accessibilityLabel, @"accessibilityLabel broken %@", hasLoadedView); - XCTAssertEqualObjects(@"Awesome things will happen", node.accessibilityHint, @"accessibilityHint broken %@", hasLoadedView); - XCTAssertEqualObjects(@"1 of 2", node.accessibilityValue, @"accessibilityValue broken %@", hasLoadedView); - XCTAssertEqual(UIAccessibilityTraitSelected | UIAccessibilityTraitButton, node.accessibilityTraits, @"accessibilityTraits broken %@", hasLoadedView); - XCTAssertTrue(CGRectEqualToRect(CGRectMake(1, 2, 3, 4), node.accessibilityFrame), @"accessibilityFrame broken %@", hasLoadedView); - XCTAssertEqualObjects(@"mas", node.accessibilityLanguage, @"accessibilityLanguage broken %@", hasLoadedView); - XCTAssertEqual(YES, node.accessibilityElementsHidden, @"accessibilityElementsHidden broken %@", hasLoadedView); - XCTAssertEqual(YES, node.accessibilityViewIsModal, @"accessibilityViewIsModal broken %@", hasLoadedView); - XCTAssertEqual(YES, node.shouldGroupAccessibilityChildren, @"shouldGroupAccessibilityChildren broken %@", hasLoadedView); } } @@ -458,21 +460,25 @@ for (ASDisplayNode *n in @[ nodes ]) {\ node.asyncdisplaykit_asyncTransactionContainer = YES; node.userInteractionEnabled = NO; node.name = @"quack like a duck"; + + node.isAccessibilityElement = YES; + node.accessibilityLabel = @"Ship love"; + node.accessibilityHint = @"Awesome things will happen"; + node.accessibilityValue = @"1 of 2"; + node.accessibilityTraits = UIAccessibilityTraitSelected | UIAccessibilityTraitButton; + node.accessibilityFrame = CGRectMake(1, 2, 3, 4); + node.accessibilityLanguage = @"mas"; + node.accessibilityElementsHidden = YES; + node.accessibilityViewIsModal = YES; + node.shouldGroupAccessibilityChildren = YES; + node.accessibilityNavigationStyle = UIAccessibilityNavigationStyleSeparate; + node.accessibilityActivationPoint = CGPointMake(1.0, 1.0); + node.accessibilityPath = [UIBezierPath bezierPath]; if (!isLayerBacked) { node.exclusiveTouch = YES; node.autoresizesSubviews = NO; node.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; - node.isAccessibilityElement = YES; - node.accessibilityLabel = @"Ship love"; - node.accessibilityHint = @"Awesome things will happen"; - node.accessibilityValue = @"1 of 2"; - node.accessibilityTraits = UIAccessibilityTraitSelected | UIAccessibilityTraitButton; - node.accessibilityFrame = CGRectMake(1, 2, 3, 4); - node.accessibilityLanguage = @"mas"; - node.accessibilityElementsHidden = YES; - node.accessibilityViewIsModal = YES; - node.shouldGroupAccessibilityChildren = YES; } }]; diff --git a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m b/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m index 5a1d967787..7fef1de042 100644 --- a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m +++ b/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m @@ -10,6 +10,7 @@ #import #import +#import #import diff --git a/AsyncDisplayKitTests/ASRelativeLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASRelativeLayoutSpecSnapshotTests.mm new file mode 100644 index 0000000000..213045ea68 --- /dev/null +++ b/AsyncDisplayKitTests/ASRelativeLayoutSpecSnapshotTests.mm @@ -0,0 +1,129 @@ +/* + * 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 "ASRelativeLayoutSpec.h" +#import "ASStackLayoutSpec.h" + +static const ASSizeRange kSize = {{100, 120}, {320, 160}}; + +@interface ASRelativeLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase +@end + +@implementation ASRelativeLayoutSpecSnapshotTests + +- (void)setUp +{ + [super setUp]; + self.recordMode = NO; +} + +- (void)testWithOptions +{ + + [self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionStart]; + [self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionCenter]; + [self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionEnd]; + +} + +- (void)testAllVerticalPositionsForHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition { + [self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionStart sizingOptions:{}]; + [self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionCenter sizingOptions:{}]; + [self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionEnd sizingOptions:{}]; +} + +- (void)testWithSizingOptions +{ + [self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart + verticalPosition:ASRelativeLayoutSpecPositionStart + sizingOptions:ASRelativeLayoutSpecSizingOptionDefault]; + [self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart + verticalPosition:ASRelativeLayoutSpecPositionStart + sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumWidth]; + [self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart + verticalPosition:ASRelativeLayoutSpecPositionStart + sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumHeight]; + [self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart + verticalPosition:ASRelativeLayoutSpecPositionStart + sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumSize]; +} + +- (void)testWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition + verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition + sizingOptions:(ASRelativeLayoutSpecSizingOption)sizingOptions +{ + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor]); + foregroundNode.staticSize = {70, 100}; + + ASLayoutSpec *layoutSpec = + [ASBackgroundLayoutSpec + backgroundLayoutSpecWithChild: + [ASRelativeLayoutSpec + relativePositionLayoutSpecWithHorizontalPosition:horizontalPosition verticalPosition:verticalPosition sizingOption:sizingOptions child:foregroundNode] + background:backgroundNode]; + + [self testLayoutSpec:layoutSpec + sizeRange:kSize + subnodes:@[backgroundNode, foregroundNode] + identifier:suffixForPositionOptions(horizontalPosition, verticalPosition, sizingOptions)]; +} + +static NSString *suffixForPositionOptions(ASRelativeLayoutSpecPosition horizontalPosition, + ASRelativeLayoutSpecPosition verticalPosition, + ASRelativeLayoutSpecSizingOption sizingOptions) +{ + NSMutableString *suffix = [NSMutableString string]; + + if ((horizontalPosition & ASRelativeLayoutSpecPositionCenter) != 0) { + [suffix appendString:@"CenterX"]; + } else if ((horizontalPosition & ASRelativeLayoutSpecPositionEnd) != 0) { + [suffix appendString:@"EndX"]; + } + + if ((verticalPosition & ASRelativeLayoutSpecPositionCenter) != 0) { + [suffix appendString:@"CenterY"]; + } else if ((verticalPosition & ASRelativeLayoutSpecPositionEnd) != 0) { + [suffix appendString:@"EndY"]; + } + + if ((sizingOptions & ASRelativeLayoutSpecSizingOptionMinimumWidth) != 0) { + [suffix appendString:@"SizingMinimumWidth"]; + } + + if ((sizingOptions & ASRelativeLayoutSpecSizingOptionMinimumHeight) != 0) { + [suffix appendString:@"SizingMinimumHeight"]; + } + + return suffix; +} + +- (void)testMinimumSizeRangeIsGivenToChildWhenNotPositioning +{ + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + foregroundNode.staticSize = {10, 10}; + foregroundNode.flexGrow = YES; + + ASLayoutSpec *childSpec = [ASBackgroundLayoutSpec + backgroundLayoutSpecWithChild:[ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart children:@[foregroundNode]] + background:backgroundNode]; + + ASRelativeLayoutSpec *layoutSpec = [ASRelativeLayoutSpec + relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionStart verticalPosition:ASRelativeLayoutSpecPositionStart sizingOption:{} child:childSpec]; + + + [self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier:nil]; +} + +@end diff --git a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm index ba98a1f76a..d5ca9074aa 100644 --- a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm +++ b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm @@ -15,7 +15,6 @@ #import "ASBackgroundLayoutSpec.h" #import "ASRatioLayoutSpec.h" #import "ASInsetLayoutSpec.h" -#import "ASLayoutOptions.h" @interface ASStackLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase @end @@ -606,19 +605,19 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex) - (void)testHorizontalAndVerticalAlignments { - [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASAlignmentLeft itemsVerticalAlignment:ASAlignmentTop identifier:@"horizontalTopLeft"]; - [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASAlignmentMiddle itemsVerticalAlignment:ASAlignmentCenter identifier:@"horizontalCenter"]; - [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASAlignmentRight itemsVerticalAlignment:ASAlignmentBottom identifier:@"horizontalBottomRight"]; - [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASAlignmentLeft itemsVerticalAlignment:ASAlignmentTop identifier:@"verticalTopLeft"]; - [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASAlignmentMiddle itemsVerticalAlignment:ASAlignmentCenter identifier:@"verticalCenter"]; - [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASAlignmentRight itemsVerticalAlignment:ASAlignmentBottom identifier:@"verticalBottomRight"]; + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASHorizontalAlignmentLeft itemsVerticalAlignment:ASVerticalAlignmentTop identifier:@"horizontalTopLeft"]; + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASHorizontalAlignmentMiddle itemsVerticalAlignment:ASVerticalAlignmentCenter identifier:@"horizontalCenter"]; + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASHorizontalAlignmentRight itemsVerticalAlignment:ASVerticalAlignmentBottom identifier:@"horizontalBottomRight"]; + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASHorizontalAlignmentLeft itemsVerticalAlignment:ASVerticalAlignmentTop identifier:@"verticalTopLeft"]; + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASHorizontalAlignmentMiddle itemsVerticalAlignment:ASVerticalAlignmentCenter identifier:@"verticalCenter"]; + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASHorizontalAlignmentRight itemsVerticalAlignment:ASVerticalAlignmentBottom identifier:@"verticalBottomRight"]; } - (void)testDirectionChangeAfterSettingHorizontalAndVerticalAlignments { ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; // Default direction is horizontal - stackLayoutSpec.horizontalAlignment = ASAlignmentRight; - stackLayoutSpec.verticalAlignment = ASAlignmentCenter; + stackLayoutSpec.horizontalAlignment = ASHorizontalAlignmentRight; + stackLayoutSpec.verticalAlignment = ASVerticalAlignmentCenter; XCTAssertEqual(stackLayoutSpec.alignItems, ASStackLayoutAlignItemsCenter); XCTAssertEqual(stackLayoutSpec.justifyContent, ASStackLayoutJustifyContentEnd); @@ -636,8 +635,8 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex) stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentEnd; // Set alignments and assert that assertions are thrown - stackLayoutSpec.horizontalAlignment = ASAlignmentMiddle; - stackLayoutSpec.verticalAlignment = ASAlignmentCenter; + stackLayoutSpec.horizontalAlignment = ASHorizontalAlignmentMiddle; + stackLayoutSpec.verticalAlignment = ASVerticalAlignmentCenter; XCTAssertThrows(stackLayoutSpec.alignItems = ASStackLayoutAlignItemsEnd); XCTAssertThrows(stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentEnd); diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index 550f3fec9d..2949a16e2f 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -144,18 +144,6 @@ @implementation ASTableViewTests -- (void)setUp -{ - /// Load a display node before the first test. - /// Without this, running this suite specifically - /// (as opposed to all tests) will cause a deadlock - /// because of the dispatch_sync in `ASScreenScale()`. - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [ASDisplayNode new]; - }); -} - // TODO: Convert this to ARC. - (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate { diff --git a/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm b/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm index 6085a88b79..2aba8a3748 100644 --- a/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm +++ b/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm @@ -8,7 +8,7 @@ #import -#import "ASTextKitHelpers.h" +#import "ASTextKitComponents.h" #import "ASTextNodeTypes.h" #import "ASTextNodeWordKerner.h" diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 465636f21e..f9f3acd9d9 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -8,33 +8,30 @@ #import #import -#import "ASVideoNode.h" +#import +#import @interface ASVideoNodeTests : XCTestCase { ASVideoNode *_videoNode; - AVAsset *_firstAsset; + AVURLAsset *_firstAsset; AVAsset *_secondAsset; + NSURL *_url; } @end @interface ASVideoNode () { ASDisplayNode *_playerNode; + AVPlayer *_player; } -@property (atomic) ASInterfaceState interfaceState; -@property (atomic) ASDisplayNode *spinner; -@property (atomic) ASDisplayNode *playerNode; -@property (atomic) BOOL shouldBePlaying; +@property (atomic, readwrite) ASInterfaceState interfaceState; +@property (atomic, readonly) ASDisplayNode *spinner; +@property (atomic, readonly) ASImageNode *placeholderImageNode; +@property (atomic, readwrite) ASDisplayNode *playerNode; +@property (atomic, readwrite) AVPlayer *player; +@property (atomic, readonly) BOOL shouldBePlaying; -- (void)setPlayerNode:(ASDisplayNode *)playerNode; -@end - -@implementation ASVideoNode (Test) - -- (void)setPlayerNode:(ASDisplayNode *)playerNode -{ - _playerNode = playerNode; -} +- (void)setPlaceholderImage:(UIImage *)image; @end @@ -43,104 +40,151 @@ - (void)setUp { _videoNode = [[ASVideoNode alloc] init]; - _firstAsset = [AVAsset assetWithURL:[NSURL URLWithString:@"firstURL"]]; + _firstAsset = [AVURLAsset assetWithURL:[NSURL URLWithString:@"firstURL"]]; _secondAsset = [AVAsset assetWithURL:[NSURL URLWithString:@"secondURL"]]; + _url = [NSURL URLWithString:@"testURL"]; } -- (void)testVideoNodeReplacesAVPlayerItemWhenNewURLIsSet -{ - _videoNode.interfaceState = ASInterfaceStateFetchData; - _videoNode.asset = _firstAsset; - - AVPlayerItem *item = [_videoNode currentItem]; - - _videoNode.asset = _secondAsset; - AVPlayerItem *secondItem = [_videoNode currentItem]; - - XCTAssertNotEqualObjects(item, secondItem); -} - -- (void)testVideoNodeDoesNotReplaceAVPlayerItemWhenSameURLIsSet -{ - _videoNode.interfaceState = ASInterfaceStateFetchData; - - _videoNode.asset = _firstAsset; - AVPlayerItem *item = [_videoNode currentItem]; - - _videoNode.asset = _firstAsset; - AVPlayerItem *secondItem = [_videoNode currentItem]; - - XCTAssertEqualObjects(item, secondItem); -} - (void)testSpinnerDefaultsToNil { XCTAssertNil(_videoNode.spinner); } + - (void)testOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnode { - _videoNode.interfaceState = ASInterfaceStateFetchData; _videoNode.asset = _firstAsset; - + [self doOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl]; +} + +- (void)testOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl +{ + _videoNode.asset = [AVAsset assetWithURL:_url]; + [self doOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl]; +} + +- (void)doOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl +{ + _videoNode.interfaceState = ASInterfaceStateFetchData; [_videoNode play]; XCTAssertNotNil(_videoNode.spinner); } + - (void)testOnPauseSpinnerIsPausedIfPresent { - _videoNode.interfaceState = ASInterfaceStateFetchData; _videoNode.asset = _firstAsset; + [self doOnPauseSpinnerIsPausedIfPresentWithURL]; +} + +- (void)testOnPauseSpinnerIsPausedIfPresentWithURL +{ + _videoNode.asset = [AVAsset assetWithURL:_url]; + [self doOnPauseSpinnerIsPausedIfPresentWithURL]; +} + +- (void)doOnPauseSpinnerIsPausedIfPresentWithURL +{ + _videoNode.interfaceState = ASInterfaceStateFetchData; [_videoNode play]; - [_videoNode pause]; XCTAssertFalse(((UIActivityIndicatorView *)_videoNode.spinner.view).isAnimating); } + - (void)testOnVideoReadySpinnerIsStoppedAndRemoved { - _videoNode.interfaceState = ASInterfaceStateFetchData; _videoNode.asset = _firstAsset; + [self doOnVideoReadySpinnerIsStoppedAndRemovedWithURL]; +} +- (void)testOnVideoReadySpinnerIsStoppedAndRemovedWithURL +{ + _videoNode.asset = [AVAsset assetWithURL:_url]; + [self doOnVideoReadySpinnerIsStoppedAndRemovedWithURL]; +} + +- (void)doOnVideoReadySpinnerIsStoppedAndRemovedWithURL +{ + _videoNode.interfaceState = ASInterfaceStateFetchData; + [_videoNode play]; - [_videoNode observeValueForKeyPath:@"status" ofObject:[_videoNode currentItem] change:@{@"new" : @(AVPlayerItemStatusReadyToPlay)} context:NULL]; + [_videoNode observeValueForKeyPath:@"status" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @(AVPlayerItemStatusReadyToPlay)} context:NULL]; XCTAssertFalse(((UIActivityIndicatorView *)_videoNode.spinner.view).isAnimating); } + - (void)testPlayerDefaultsToNil { + _videoNode.asset = _firstAsset; + XCTAssertNil(_videoNode.player); +} + +- (void)testPlayerDefaultsToNilWithURL +{ + _videoNode.asset = [AVAsset assetWithURL:_url]; XCTAssertNil(_videoNode.player); } - (void)testPlayerIsCreatedInFetchData { _videoNode.asset = _firstAsset; - _videoNode.interfaceState = ASInterfaceStateFetchData; XCTAssertNotNil(_videoNode.player); } +- (void)testPlayerIsCreatedInFetchDataWithURL +{ + _videoNode.asset = [AVAsset assetWithURL:_url]; + _videoNode.interfaceState = ASInterfaceStateFetchData; + + XCTAssertNotNil(_videoNode.player); +} + + - (void)testPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlaying { _videoNode.asset = _firstAsset; + [self doPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL]; +} +- (void)testPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL +{ + _videoNode.asset = [AVAsset assetWithURL:_url]; + [self doPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL]; +} + +- (void)doPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL +{ [_videoNode setInterfaceState:ASInterfaceStateNone]; [_videoNode didLoad]; XCTAssert(![_videoNode.subnodes containsObject:_videoNode.playerNode]); } + - (void)testPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying { _videoNode.asset = _firstAsset; + [self doPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying]; +} +- (void)testPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlayingWithUrl +{ + _videoNode.asset = [AVAsset assetWithURL:_url]; + [self doPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying]; +} + +- (void)doPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying +{ [_videoNode pause]; - [_videoNode setInterfaceState:ASInterfaceStateVisible]; + [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay]; [_videoNode didLoad]; XCTAssert(![_videoNode.subnodes containsObject:_videoNode.playerNode]); @@ -150,6 +194,17 @@ - (void)testVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay { _videoNode.asset = _firstAsset; + [self doVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay]; +} + +- (void)testVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplayWithURL +{ + _videoNode.asset = [AVAsset assetWithURL:_url]; + [self doVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay]; +} + +- (void)doVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay +{ _videoNode.shouldAutoplay = YES; _videoNode.playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; @@ -162,9 +217,21 @@ XCTAssertTrue(_videoNode.shouldBePlaying); } + - (void)testVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater { _videoNode.asset = _firstAsset; + [self doVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater]; +} + +- (void)testVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLaterWithURL +{ + _videoNode.asset = [AVAsset assetWithURL:_url]; + [self doVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater]; +} + +- (void)doVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater +{ [_videoNode play]; [_videoNode interfaceStateDidChange:ASInterfaceStateNone fromState:ASInterfaceStateVisible]; @@ -173,9 +240,21 @@ XCTAssertTrue(_videoNode.shouldBePlaying); } + - (void)testVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack { _videoNode.asset = _firstAsset; + [self doVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack]; +} + +- (void)testVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBackWithURL +{ + _videoNode.asset = [AVAsset assetWithURL:_url]; + [self doVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack]; +} + +- (void)doVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack +{ [_videoNode play]; [_videoNode interfaceStateDidChange:ASInterfaceStateVisible fromState:ASInterfaceStateNone]; @@ -184,4 +263,103 @@ XCTAssertTrue(_videoNode.shouldBePlaying); } +- (void)testMutingShouldMutePlayer +{ + [_videoNode setPlayer:[[AVPlayer alloc] init]]; + + _videoNode.muted = YES; + + XCTAssertTrue(_videoNode.player.muted); +} + +- (void)testUnMutingShouldUnMutePlayer +{ + [_videoNode setPlayer:[[AVPlayer alloc] init]]; + + _videoNode.muted = YES; + _videoNode.muted = NO; + + XCTAssertFalse(_videoNode.player.muted); +} + +- (void)testVideoThatDoesNotAutorepeatsShouldPauseOnPlaybackEnd +{ + _videoNode.asset = _firstAsset; + _videoNode.shouldAutorepeat = NO; + + [_videoNode didLoad]; + [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData]; + [_videoNode play]; + + XCTAssertTrue(_videoNode.isPlaying); + + [[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:_videoNode.currentItem]; + + XCTAssertFalse(_videoNode.isPlaying); + XCTAssertEqual(0, CMTimeGetSeconds(_videoNode.player.currentTime)); +} + +- (void)testVideoThatAutorepeatsShouldRepeatOnPlaybackEnd +{ + _videoNode.asset = _firstAsset; + _videoNode.shouldAutorepeat = YES; + + [_videoNode didLoad]; + [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData]; + [_videoNode play]; + + [[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:_videoNode.currentItem]; + + XCTAssertTrue(_videoNode.isPlaying); +} + +- (void)testBackgroundingAndForegroungingTheAppShouldPauseAndResume +{ + _videoNode.asset = _firstAsset; + + [_videoNode didLoad]; + [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData]; + [_videoNode play]; + + XCTAssertTrue(_videoNode.isPlaying); + + [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil]; + + XCTAssertFalse(_videoNode.isPlaying); + + [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; + + XCTAssertTrue(_videoNode.isPlaying); +} + +- (void)testSettingVideoGravityChangesPlaceholderContentMode +{ + [_videoNode setPlaceholderImage:[[UIImage alloc] init]]; + XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.placeholderImageNode.contentMode); + + _videoNode.gravity = AVLayerVideoGravityResize; + XCTAssertEqual(UIViewContentModeScaleToFill, _videoNode.placeholderImageNode.contentMode); + + _videoNode.gravity = AVLayerVideoGravityResizeAspect; + XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.placeholderImageNode.contentMode); + + _videoNode.gravity = AVLayerVideoGravityResizeAspectFill; + XCTAssertEqual(UIViewContentModeScaleAspectFill, _videoNode.placeholderImageNode.contentMode); +} + +- (void)testChangingPlayButtonPerformsProperCleanup +{ + ASButtonNode *firstButton = _videoNode.playButton; + XCTAssertTrue([firstButton.allTargets containsObject:_videoNode]); + + ASButtonNode *secondButton = [[ASButtonNode alloc] init]; + _videoNode.playButton = secondButton; + + XCTAssertTrue([secondButton.allTargets containsObject:_videoNode]); + XCTAssertEqual(_videoNode, secondButton.supernode); + + XCTAssertFalse([firstButton.allTargets containsObject:_videoNode]); + XCTAssertNotEqual(_videoNode, firstButton.supernode); +} + @end diff --git a/AsyncDisplayKitTests/ArrayDiffingTests.m b/AsyncDisplayKitTests/ArrayDiffingTests.m index 636af90835..aaac8e4ca1 100644 --- a/AsyncDisplayKitTests/ArrayDiffingTests.m +++ b/AsyncDisplayKitTests/ArrayDiffingTests.m @@ -10,13 +10,58 @@ #import "NSArray+Diffing.h" +@interface NSArray (ArrayDiffingTests) +- (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL (^)(id lhs, id rhs))comparison; +@end + @interface ArrayDiffingTests : XCTestCase @end @implementation ArrayDiffingTests -- (void)testDiffing { +- (void)testDiffingCommonIndexes +{ + NSArray *tests = @[ + @[ + @[@"bob", @"alice", @"dave"], + @[@"bob", @"alice", @"dave", @"gary"], + @[@0, @1, @2] + ], + @[ + @[@"bob", @"alice", @"dave"], + @[@"bob", @"gary", @"dave"], + @[@0, @2] + ], + @[ + @[@"bob", @"alice"], + @[@"gary", @"dave"], + @[], + ], + @[ + @[@"bob", @"alice", @"dave"], + @[], + @[], + ], + @[ + @[], + @[@"bob", @"alice", @"dave"], + @[], + ], + ]; + + for (NSArray *test in tests) { + NSIndexSet *indexSet = [test[0] _asdk_commonIndexesWithArray:test[1] compareBlock:^BOOL(id lhs, id rhs) { + return [lhs isEqual:rhs]; + }]; + + for (NSNumber *index in (NSArray *)test[2]) { + XCTAssert([indexSet containsIndex:[index integerValue]]); + } + } +} + +- (void)testDiffingInsertionsAndDeletions { NSArray *tests = @[ @[ @[@"bob", @"alice", @"dave"], diff --git a/AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist b/AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist index c317ef5221..169b6f710e 100644 --- a/AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist +++ b/AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.facebook.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png new file mode 100644 index 0000000000..02717f8fdb Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png new file mode 100644 index 0000000000..50cb613c24 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png new file mode 100644 index 0000000000..69e7392384 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@2x.png new file mode 100644 index 0000000000..311ef9ed32 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png new file mode 100644 index 0000000000..385fc3e817 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png new file mode 100644 index 0000000000..28036afad5 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png new file mode 100644 index 0000000000..692c2a4e84 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@2x.png new file mode 100644 index 0000000000..04ee2d6115 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@2x.png new file mode 100644 index 0000000000..3d9d16b5d2 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png new file mode 100644 index 0000000000..7011b35abf Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png new file mode 100644 index 0000000000..50cb613c24 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png new file mode 100644 index 0000000000..b14c267b42 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@2x.png new file mode 100644 index 0000000000..270b15feb6 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@2x.png new file mode 100644 index 0000000000..7fddbff94e Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@3x.png new file mode 100644 index 0000000000..3fb6feee42 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions@3x.png new file mode 100644 index 0000000000..c2bf62b078 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@3x.png new file mode 100644 index 0000000000..f1d9c2e52f Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@3x.png new file mode 100644 index 0000000000..8093c89761 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@3x.png new file mode 100644 index 0000000000..d9dab946b4 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@3x.png new file mode 100644 index 0000000000..7e824c2073 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@3x.png new file mode 100644 index 0000000000..9064f33704 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@3x.png new file mode 100644 index 0000000000..e634d8e090 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@3x.png new file mode 100644 index 0000000000..448ad0d27d Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@3x.png new file mode 100644 index 0000000000..9f88fcc02d Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@3x.png new file mode 100644 index 0000000000..c2bf62b078 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@3x.png new file mode 100644 index 0000000000..802dbc7bca Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@3x.png new file mode 100644 index 0000000000..6796058c28 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@3x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@3x.png new file mode 100644 index 0000000000..d4a15f979b Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@3x.png differ diff --git a/Base/ASAssert.h b/Base/ASAssert.h index b0b53cc855..446f64b4b7 100644 --- a/Base/ASAssert.h +++ b/Base/ASAssert.h @@ -9,6 +9,7 @@ #pragma once #import +#import #define ASDisplayNodeAssertWithSignalAndLogFunction(condition, description, logFunction, ...) NSAssert(condition, description, ##__VA_ARGS__); #define ASDisplayNodeCAssertWithSignalAndLogFunction(condition, description, logFunction, ...) NSCAssert(condition, description, ##__VA_ARGS__); @@ -30,11 +31,11 @@ #define ASDisplayNodeAssertNotInstantiable() ASDisplayNodeAssertWithSignal(NO, nil, @"This class is not instantiable."); #define ASDisplayNodeAssertNotSupported() ASDisplayNodeAssertWithSignal(NO, nil, @"This method is not supported by class %@", [self class]); -#define ASDisplayNodeAssertMainThread() ASDisplayNodeAssertWithSignal([NSThread isMainThread], nil, @"This method must be called on the main thread") -#define ASDisplayNodeCAssertMainThread() ASDisplayNodeCAssertWithSignal([NSThread isMainThread], nil, @"This function must be called on the main thread") +#define ASDisplayNodeAssertMainThread() ASDisplayNodeAssertWithSignal(0 != pthread_main_np(), nil, @"This method must be called on the main thread") +#define ASDisplayNodeCAssertMainThread() ASDisplayNodeCAssertWithSignal(0 != pthread_main_np(), nil, @"This function must be called on the main thread") -#define ASDisplayNodeAssertNotMainThread() ASDisplayNodeAssertWithSignal(![NSThread isMainThread], nil, @"This method must be called off the main thread") -#define ASDisplayNodeCAssertNotMainThread() ASDisplayNodeCAssertWithSignal(![NSThread isMainThread], nil, @"This function must be called off the main thread") +#define ASDisplayNodeAssertNotMainThread() ASDisplayNodeAssertWithSignal(0 == pthread_main_np(), nil, @"This method must be called off the main thread") +#define ASDisplayNodeCAssertNotMainThread() ASDisplayNodeCAssertWithSignal(0 == pthread_main_np(), nil, @"This function must be called off the main thread") #define ASDisplayNodeAssertFlag(X) ASDisplayNodeAssertWithSignal((1 == __builtin_popcount(X)), nil, nil) #define ASDisplayNodeCAssertFlag(X) ASDisplayNodeCAssertWithSignal((1 == __builtin_popcount(X)), nil, nil) diff --git a/Base/ASEqualityHelpers.h b/Base/ASEqualityHelpers.h index 84a4658db8..f6dd4de1b7 100644 --- a/Base/ASEqualityHelpers.h +++ b/Base/ASEqualityHelpers.h @@ -11,12 +11,10 @@ /** @abstract Correctly equates two objects, including cases where both objects are nil. The latter is a case where `isEqual:` fails. @param obj The first object in the comparison. Can be nil. - @param obj The second object in the comparison. Can be nil. + @param otherObj The second object in the comparison. Can be nil. @result YES if the objects are equal, including cases where both object are nil. */ ASDISPLAYNODE_INLINE BOOL ASObjectIsEqual(id obj, id otherObj) { - if (obj == otherObj) - return YES; - return [obj isEqual:otherObj]; + return obj == otherObj || [obj isEqual:otherObj]; } diff --git a/README.md b/README.md index 68c98c516b..e4d1dcd36f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,16 @@ ![AsyncDisplayKit](https://github.com/facebook/AsyncDisplayKit/blob/master/docs/assets/logo.png) -[![Build Status](https://travis-ci.org/facebook/AsyncDisplayKit.svg)](https://travis-ci.org/facebook/AsyncDisplayKit) -[![Coverage Status](https://coveralls.io/repos/facebook/AsyncDisplayKit/badge.svg?branch=master)](https://coveralls.io/r/facebook/AsyncDisplayKit?branch=master) +[![Apps Using](https://img.shields.io/badge/Apps%20Using%20ASDK-%3E3,178-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit) +[![Downloads](https://img.shields.io/badge/Total%20Downloads-%3E336,372-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit) + +[![Platform](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS-orange.svg)](http://AsyncDisplayKit.org) +[![Languages](https://img.shields.io/badge/languages-ObjC%20%7C%20Swift-orange.svg)](http://AsyncDisplayKit.org) + [![Version](https://img.shields.io/cocoapods/v/AsyncDisplayKit.svg)](http://cocoapods.org/pods/AsyncDisplayKit) -[![Platform](https://img.shields.io/cocoapods/p/AsyncDisplayKit.svg)]() +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-59C939.svg?style=flat)](https://github.com/Carthage/Carthage) +[![Build Status](https://travis-ci.org/facebook/AsyncDisplayKit.svg)](https://travis-ci.org/facebook/AsyncDisplayKit) [![License](https://img.shields.io/cocoapods/l/AsyncDisplayKit.svg)](https://github.com/facebook/AsyncDisplayKit/blob/master/LICENSE) -[![Downloads](https://img.shields.io/badge/downloads-%3E120k-green.svg)](http://cocoapods.org/pods/AsyncDisplayKit) + AsyncDisplayKit is an iOS framework that keeps even the most complex user interfaces smooth and responsive. It was originally built to make Facebook's @@ -94,9 +99,9 @@ to implement node hierarchies or custom drawing. ### Learn more -* Read the [Getting Started guide](http://asyncdisplaykit.org/guide/) +* Read the [Getting Started guide](http://asyncdisplaykit.org/docs/getting-started.html) * Get the [sample projects](https://github.com/facebook/AsyncDisplayKit/tree/master/examples) -* Browse the [API reference](http://asyncdisplaykit.org/appledoc/) +* Browse the [API reference](http://asyncdisplaykit.org/appledocs.html) * Watch the [NSLondon talk](http://vimeo.com/103589245) or the [NSSpain talk](https://www.youtube.com/watch?v=RY_X7l1g79Q) ## Testing diff --git a/build.sh b/build.sh index d2f19504d3..c3744eab05 100755 --- a/build.sh +++ b/build.sh @@ -1,8 +1,8 @@ #!/bin/bash # **** Update me when new Xcode versions are released! **** -PLATFORM="platform=iOS Simulator,OS=9.2,name=iPhone 6" -SDK="iphonesimulator9.2" +PLATFORM="platform=iOS Simulator,OS=9.3,name=iPhone 6" +SDK="iphonesimulator9.3" # It is pitch black. @@ -35,13 +35,35 @@ if [ "$MODE" = "examples" ]; then for example in examples/*/; do echo "Building $example." - pod install --project-directory=$example - xctool \ - -workspace "${example}Sample.xcworkspace" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build + + if [ -f "${example}/Podfile" ]; then + echo "Using CocoaPods" + pod install --project-directory=$example + + xctool \ + -workspace "${example}Sample.xcworkspace" \ + -scheme Sample \ + -sdk "$SDK" \ + -destination "$PLATFORM" \ + build + elif [ -f "${example}/Cartfile" ]; then + echo "Using Carthage" + local_repo=`pwd` + current_branch=`git rev-parse --abbrev-ref HEAD` + cd $example + + echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile" + carthage update --platform iOS + + xctool \ + -project "Sample.xcodeproj" \ + -scheme Sample \ + -sdk "$SDK" \ + -destination "$PLATFORM" \ + build + + cd ../.. + fi done trap - EXIT exit 0 diff --git a/examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.h b/examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.h new file mode 100644 index 0000000000..b3ea365221 --- /dev/null +++ b/examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.h @@ -0,0 +1,17 @@ +// +// AppDelegate.h +// ASAnimatedImage +// +// Created by Garrett Moon on 3/22/16. +// Copyright © 2016 Facebook, Inc. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.m b/examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.m new file mode 100644 index 0000000000..b4a601de56 --- /dev/null +++ b/examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.m @@ -0,0 +1,45 @@ +// +// AppDelegate.m +// ASAnimatedImage +// +// Created by Garrett Moon on 3/22/16. +// Copyright © 2016 Facebook, Inc. All rights reserved. +// + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/examples/ASAnimatedImage/ASAnimatedImage/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/ASAnimatedImage/ASAnimatedImage/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..36d2c80d88 --- /dev/null +++ b/examples/ASAnimatedImage/ASAnimatedImage/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASAnimatedImage/ASAnimatedImage/Base.lproj/LaunchScreen.storyboard b/examples/ASAnimatedImage/ASAnimatedImage/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..2e721e1833 --- /dev/null +++ b/examples/ASAnimatedImage/ASAnimatedImage/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASAnimatedImage/ASAnimatedImage/Base.lproj/Main.storyboard b/examples/ASAnimatedImage/ASAnimatedImage/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..f56d2f3bb5 --- /dev/null +++ b/examples/ASAnimatedImage/ASAnimatedImage/Base.lproj/Main.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASAnimatedImage/ASAnimatedImage/Info.plist b/examples/ASAnimatedImage/ASAnimatedImage/Info.plist new file mode 100644 index 0000000000..40c6215d90 --- /dev/null +++ b/examples/ASAnimatedImage/ASAnimatedImage/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/ASAnimatedImage/ASAnimatedImage/ViewController.h b/examples/ASAnimatedImage/ASAnimatedImage/ViewController.h new file mode 100644 index 0000000000..5fce20369d --- /dev/null +++ b/examples/ASAnimatedImage/ASAnimatedImage/ViewController.h @@ -0,0 +1,15 @@ +// +// ViewController.h +// ASAnimatedImage +// +// Created by Garrett Moon on 3/22/16. +// Copyright © 2016 Facebook, Inc. All rights reserved. +// + +#import + +@interface ViewController : UIViewController + + +@end + diff --git a/examples/ASAnimatedImage/ASAnimatedImage/ViewController.m b/examples/ASAnimatedImage/ASAnimatedImage/ViewController.m new file mode 100644 index 0000000000..9087dfdd5d --- /dev/null +++ b/examples/ASAnimatedImage/ASAnimatedImage/ViewController.m @@ -0,0 +1,37 @@ +// +// ViewController.m +// ASAnimatedImage +// +// Created by Garrett Moon on 3/22/16. +// Copyright © 2016 Facebook, Inc. All rights reserved. +// + +#import "ViewController.h" + +#import + +@interface ViewController () + +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. + + ASNetworkImageNode *imageNode = [[ASNetworkImageNode alloc] init]; + imageNode.URL = [NSURL URLWithString:@"https://s-media-cache-ak0.pinimg.com/originals/07/44/38/074438e7c75034df2dcf37ba1057803e.gif"]; + imageNode.frame = self.view.bounds; + imageNode.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + imageNode.contentMode = UIViewContentModeScaleAspectFit; + + [self.view addSubnode:imageNode]; +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +@end diff --git a/examples/ASAnimatedImage/ASAnimatedImage/main.m b/examples/ASAnimatedImage/ASAnimatedImage/main.m new file mode 100644 index 0000000000..dd6cdbdb8a --- /dev/null +++ b/examples/ASAnimatedImage/ASAnimatedImage/main.m @@ -0,0 +1,16 @@ +// +// main.m +// ASAnimatedImage +// +// Created by Garrett Moon on 3/22/16. +// Copyright © 2016 Facebook, Inc. All rights reserved. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/ASAnimatedImage/Podfile b/examples/ASAnimatedImage/Podfile new file mode 100644 index 0000000000..5e6d5cc415 --- /dev/null +++ b/examples/ASAnimatedImage/Podfile @@ -0,0 +1,4 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +pod 'AsyncDisplayKit', :path => '../..' +pod 'PINRemoteImage', :git => 'https://github.com/pinterest/PINRemoteImage.git', :branch => 'addPINAnimatedImage' diff --git a/examples/ASAnimatedImage/Sample.xcodeproj/project.pbxproj b/examples/ASAnimatedImage/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..eaaaed2bad --- /dev/null +++ b/examples/ASAnimatedImage/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,385 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 683ADBA31CA19883005863A4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 683ADBA21CA19883005863A4 /* main.m */; }; + 683ADBA61CA19883005863A4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 683ADBA51CA19883005863A4 /* AppDelegate.m */; }; + 683ADBA91CA19883005863A4 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 683ADBA81CA19883005863A4 /* ViewController.m */; }; + 683ADBAC1CA19883005863A4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 683ADBAA1CA19883005863A4 /* Main.storyboard */; }; + 683ADBAE1CA19883005863A4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 683ADBAD1CA19883005863A4 /* Assets.xcassets */; }; + 683ADBB11CA19883005863A4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 683ADBAF1CA19883005863A4 /* LaunchScreen.storyboard */; }; + EE964E5E7CD506D45C6DCC49 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A6F2399FA1A86586D9BDAE05 /* libPods.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 683ADB9E1CA19883005863A4 /* ASAnimatedImage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ASAnimatedImage.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 683ADBA21CA19883005863A4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 683ADBA41CA19883005863A4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 683ADBA51CA19883005863A4 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 683ADBA71CA19883005863A4 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 683ADBA81CA19883005863A4 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 683ADBAB1CA19883005863A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 683ADBAD1CA19883005863A4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 683ADBB01CA19883005863A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 683ADBB21CA19883005863A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A6F2399FA1A86586D9BDAE05 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + BBB395EF2813E7DB5CB49459 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + BBBE85D30A6D31AD7021A9AF /* 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 */ + 683ADB9B1CA19883005863A4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EE964E5E7CD506D45C6DCC49 /* libPods.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 683ADB951CA19883005863A4 = { + isa = PBXGroup; + children = ( + 683ADBA01CA19883005863A4 /* ASAnimatedImage */, + 683ADB9F1CA19883005863A4 /* Products */, + 71A772B0DB9B7760CE330DD9 /* Pods */, + 8C6AC07DE55B51935C632F56 /* Frameworks */, + ); + sourceTree = ""; + }; + 683ADB9F1CA19883005863A4 /* Products */ = { + isa = PBXGroup; + children = ( + 683ADB9E1CA19883005863A4 /* ASAnimatedImage.app */, + ); + name = Products; + sourceTree = ""; + }; + 683ADBA01CA19883005863A4 /* ASAnimatedImage */ = { + isa = PBXGroup; + children = ( + 683ADBA41CA19883005863A4 /* AppDelegate.h */, + 683ADBA51CA19883005863A4 /* AppDelegate.m */, + 683ADBA71CA19883005863A4 /* ViewController.h */, + 683ADBA81CA19883005863A4 /* ViewController.m */, + 683ADBAA1CA19883005863A4 /* Main.storyboard */, + 683ADBAD1CA19883005863A4 /* Assets.xcassets */, + 683ADBAF1CA19883005863A4 /* LaunchScreen.storyboard */, + 683ADBB21CA19883005863A4 /* Info.plist */, + 683ADBA11CA19883005863A4 /* Supporting Files */, + ); + path = ASAnimatedImage; + sourceTree = ""; + }; + 683ADBA11CA19883005863A4 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 683ADBA21CA19883005863A4 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 71A772B0DB9B7760CE330DD9 /* Pods */ = { + isa = PBXGroup; + children = ( + BBBE85D30A6D31AD7021A9AF /* Pods.debug.xcconfig */, + BBB395EF2813E7DB5CB49459 /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 8C6AC07DE55B51935C632F56 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A6F2399FA1A86586D9BDAE05 /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 683ADB9D1CA19883005863A4 /* ASAnimatedImage */ = { + isa = PBXNativeTarget; + buildConfigurationList = 683ADBB51CA19883005863A4 /* Build configuration list for PBXNativeTarget "ASAnimatedImage" */; + buildPhases = ( + 694B306B43ED1C3916B0D909 /* Check Pods Manifest.lock */, + 683ADB9A1CA19883005863A4 /* Sources */, + 683ADB9B1CA19883005863A4 /* Frameworks */, + 683ADB9C1CA19883005863A4 /* Resources */, + 26A96BEEF893B1FA39F144CF /* Embed Pods Frameworks */, + 2ADE0E7B5309A9CD043DDB3E /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ASAnimatedImage; + productName = ASAnimatedImage; + productReference = 683ADB9E1CA19883005863A4 /* ASAnimatedImage.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 683ADB961CA19883005863A4 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = "Facebook, Inc."; + TargetAttributes = { + 683ADB9D1CA19883005863A4 = { + CreatedOnToolsVersion = 7.2; + }; + }; + }; + buildConfigurationList = 683ADB991CA19883005863A4 /* Build configuration list for PBXProject "ASAnimatedImage" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 683ADB951CA19883005863A4; + productRefGroup = 683ADB9F1CA19883005863A4 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 683ADB9D1CA19883005863A4 /* ASAnimatedImage */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 683ADB9C1CA19883005863A4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 683ADBB11CA19883005863A4 /* LaunchScreen.storyboard in Resources */, + 683ADBAE1CA19883005863A4 /* Assets.xcassets in Resources */, + 683ADBAC1CA19883005863A4 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 26A96BEEF893B1FA39F144CF /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 2ADE0E7B5309A9CD043DDB3E /* 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; + }; + 694B306B43ED1C3916B0D909 /* 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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 683ADB9A1CA19883005863A4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 683ADBA91CA19883005863A4 /* ViewController.m in Sources */, + 683ADBA61CA19883005863A4 /* AppDelegate.m in Sources */, + 683ADBA31CA19883005863A4 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 683ADBAA1CA19883005863A4 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 683ADBAB1CA19883005863A4 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 683ADBAF1CA19883005863A4 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 683ADBB01CA19883005863A4 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 683ADBB31CA19883005863A4 /* 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; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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 = 9.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 683ADBB41CA19883005863A4 /* 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 = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + 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 = 9.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 683ADBB61CA19883005863A4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BBBE85D30A6D31AD7021A9AF /* Pods.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = ASAnimatedImage/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = facebook.ASAnimatedImage; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 683ADBB71CA19883005863A4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BBB395EF2813E7DB5CB49459 /* Pods.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = ASAnimatedImage/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = facebook.ASAnimatedImage; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 683ADB991CA19883005863A4 /* Build configuration list for PBXProject "ASAnimatedImage" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 683ADBB31CA19883005863A4 /* Debug */, + 683ADBB41CA19883005863A4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 683ADBB51CA19883005863A4 /* Build configuration list for PBXNativeTarget "ASAnimatedImage" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 683ADBB61CA19883005863A4 /* Debug */, + 683ADBB71CA19883005863A4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 683ADB961CA19883005863A4 /* Project object */; +} diff --git a/examples/ASAnimatedImage/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/ASAnimatedImage/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..dd7b72cdff --- /dev/null +++ b/examples/ASAnimatedImage/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/ASAnimatedImage/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/ASAnimatedImage/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..16cb2bd07c --- /dev/null +++ b/examples/ASAnimatedImage/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASAnimatedImage/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASAnimatedImage/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/ASAnimatedImage/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/ASCollectionView/Sample/PresentingViewController.m b/examples/ASCollectionView/Sample/PresentingViewController.m index 49c65e6906..80669f29b8 100644 --- a/examples/ASCollectionView/Sample/PresentingViewController.m +++ b/examples/ASCollectionView/Sample/PresentingViewController.m @@ -18,6 +18,7 @@ - (void)viewDidLoad { [super viewDidLoad]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Push Details" style:UIBarButtonItemStylePlain target:self action:@selector(pushNewViewController)]; } diff --git a/examples/ASCollectionView/Sample/SupplementaryNode.m b/examples/ASCollectionView/Sample/SupplementaryNode.m index ca5579e9a6..96e07195b0 100644 --- a/examples/ASCollectionView/Sample/SupplementaryNode.m +++ b/examples/ASCollectionView/Sample/SupplementaryNode.m @@ -17,9 +17,11 @@ static CGFloat kInsets = 15.0; -@implementation SupplementaryNode { - ASTextNode *_textNode; -} +@interface SupplementaryNode () +@property (nonatomic, strong) ASTextNode *textNode; +@end + +@implementation SupplementaryNode - (instancetype)initWithText:(NSString *)text { @@ -37,7 +39,7 @@ static CGFloat kInsets = 15.0; { ASCenterLayoutSpec *center = [[ASCenterLayoutSpec alloc] init]; center.centeringOptions = ASCenterLayoutSpecCenteringXY; - center.child = _textNode; + center.child = self.textNode; UIEdgeInsets insets = UIEdgeInsetsMake(kInsets, kInsets, kInsets, kInsets); return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:center]; } @@ -47,9 +49,9 @@ static CGFloat kInsets = 15.0; - (NSDictionary *)textAttributes { return @{ - NSFontAttributeName: [UIFont systemFontOfSize:18.0], - NSForegroundColorAttributeName: [UIColor whiteColor], - }; + NSFontAttributeName: [UIFont systemFontOfSize:18.0], + NSForegroundColorAttributeName: [UIColor whiteColor], + }; } @end diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index 00aea567dc..da9ff290e8 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -15,50 +15,48 @@ #import "SupplementaryNode.h" #import "ItemNode.h" +@interface ViewController () +@property (nonatomic, strong) ASCollectionView *collectionView; +@property (nonatomic, strong) NSArray *data; +@end + @interface ViewController () -{ - ASCollectionView *_collectionView; - NSArray *_data; -} @end @implementation ViewController -#pragma mark - -#pragma mark UIViewController. - -- (instancetype)init +- (void)dealloc { - if (!(self = [super init])) - return nil; + self.collectionView.asyncDataSource = nil; + self.collectionView.asyncDelegate = nil; - UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - layout.headerReferenceSize = CGSizeMake(50.0, 50.0); - layout.footerReferenceSize = CGSizeMake(50.0, 50.0); - - _collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - _collectionView.asyncDataSource = self; - _collectionView.asyncDelegate = self; - _collectionView.backgroundColor = [UIColor whiteColor]; - - [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; - [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter]; - -#if !SIMULATE_WEB_RESPONSE - self.navigationItem.leftItemsSupplementBackButton = YES; - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadTapped)]; -#endif - - return self; + NSLog(@"ViewController is deallocing"); } - (void)viewDidLoad { [super viewDidLoad]; - [self.view addSubview:_collectionView]; + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + layout.headerReferenceSize = CGSizeMake(50.0, 50.0); + layout.footerReferenceSize = CGSizeMake(50.0, 50.0); + + self.collectionView = [[ASCollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; + self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.collectionView.asyncDataSource = self; + self.collectionView.asyncDelegate = self; + self.collectionView.backgroundColor = [UIColor whiteColor]; + + [self.collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; + [self.collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter]; + [self.view addSubview:self.collectionView]; + +#if !SIMULATE_WEB_RESPONSE + self.navigationItem.leftItemsSupplementBackButton = YES; + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadTapped)]; +#endif #if SIMULATE_WEB_RESPONSE __weak typeof(self) weakSelf = self; @@ -86,39 +84,28 @@ #endif } -- (void)viewWillLayoutSubviews -{ - _collectionView.frame = self.view.bounds; -} - -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - - (void)reloadTapped { - [_collectionView reloadData]; + [self.collectionView reloadData]; } #pragma mark - #pragma mark ASCollectionView data source. -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath; { NSString *text = [NSString stringWithFormat:@"[%zd.%zd] says hi", indexPath.section, indexPath.item]; - return [[ItemNode alloc] initWithString:text]; + return ^{ + return [[ItemNode alloc] initWithString:text]; + }; } - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { NSString *text = [kind isEqualToString:UICollectionElementKindSectionHeader] ? @"Header" : @"Footer"; SupplementaryNode *node = [[SupplementaryNode alloc] initWithText:text]; - if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { - node.backgroundColor = [UIColor blueColor]; - } else { - node.backgroundColor = [UIColor redColor]; - } + BOOL isHeaderSection = [kind isEqualToString:UICollectionElementKindSectionHeader]; + node.backgroundColor = isHeaderSection ? [UIColor blueColor] : [UIColor redColor]; return node; } @@ -153,15 +140,9 @@ [context completeBatchFetching:YES]; } -- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { +- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section +{ return UIEdgeInsetsMake(20.0, 20.0, 20.0, 20.0); } -#if SIMULATE_WEB_RESPONSE --(void)dealloc -{ - NSLog(@"ViewController is deallocing"); -} -#endif - @end diff --git a/examples/ASDKgram/Default-568h@2x.png b/examples/ASDKgram/Default-568h@2x.png new file mode 100644 index 0000000000..6ee80b9393 Binary files /dev/null and b/examples/ASDKgram/Default-568h@2x.png differ diff --git a/examples/ASDKgram/Default-667h@2x.png b/examples/ASDKgram/Default-667h@2x.png new file mode 100644 index 0000000000..e7b975e21b Binary files /dev/null and b/examples/ASDKgram/Default-667h@2x.png differ diff --git a/examples/ASDKgram/Default-736h@3x.png b/examples/ASDKgram/Default-736h@3x.png new file mode 100644 index 0000000000..c8949cae16 Binary files /dev/null and b/examples/ASDKgram/Default-736h@3x.png differ diff --git a/examples/ASDKgram/Podfile b/examples/ASDKgram/Podfile new file mode 100644 index 0000000000..6c012e3c04 --- /dev/null +++ b/examples/ASDKgram/Podfile @@ -0,0 +1,3 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..b9241d31c6 --- /dev/null +++ b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,535 @@ +// !$*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 */; }; + 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 */; }; + 76229A781CBB79E000B62CEF /* WindowWithStatusBarUnderlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 76229A771CBB79E000B62CEF /* WindowWithStatusBarUnderlay.m */; }; + 767A5F111CAA3BFE004CDA8D /* tabBarIcons in Resources */ = {isa = PBXBuildFile; fileRef = 767A5F101CAA3BFE004CDA8D /* tabBarIcons */; }; + 767A5F131CAA3C66004CDA8D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 767A5F121CAA3C66004CDA8D /* Assets.xcassets */; }; + 768843801CAA37EF00D8629E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843681CAA37EF00D8629E /* AppDelegate.m */; }; + 768843811CAA37EF00D8629E /* CommentFeedModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843691CAA37EF00D8629E /* CommentFeedModel.m */; }; + 768843821CAA37EF00D8629E /* CommentModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688436A1CAA37EF00D8629E /* CommentModel.m */; }; + 768843831CAA37EF00D8629E /* CommentsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688436B1CAA37EF00D8629E /* CommentsNode.m */; }; + 768843841CAA37EF00D8629E /* CommentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688436C1CAA37EF00D8629E /* CommentView.m */; }; + 768843851CAA37EF00D8629E /* ImageURLModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688436D1CAA37EF00D8629E /* ImageURLModel.m */; }; + 768843881CAA37EF00D8629E /* LocationModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843701CAA37EF00D8629E /* LocationModel.m */; }; + 768843891CAA37EF00D8629E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843711CAA37EF00D8629E /* main.m */; }; + 7688438B1CAA37EF00D8629E /* PhotoCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843731CAA37EF00D8629E /* PhotoCellNode.m */; }; + 7688438C1CAA37EF00D8629E /* PhotoCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843741CAA37EF00D8629E /* PhotoCollectionViewCell.m */; }; + 7688438D1CAA37EF00D8629E /* PhotoFeedModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843751CAA37EF00D8629E /* PhotoFeedModel.m */; }; + 7688438E1CAA37EF00D8629E /* PhotoFeedNodeController.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843761CAA37EF00D8629E /* PhotoFeedNodeController.m */; }; + 768843901CAA37EF00D8629E /* PhotoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843781CAA37EF00D8629E /* PhotoModel.m */; }; + 768843911CAA37EF00D8629E /* PhotoTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843791CAA37EF00D8629E /* PhotoTableViewCell.m */; }; + 768843921CAA37EF00D8629E /* PhotoFeedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688437A1CAA37EF00D8629E /* PhotoFeedViewController.m */; }; + 768843931CAA37EF00D8629E /* UserModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688437B1CAA37EF00D8629E /* UserModel.m */; }; + 768843961CAA37EF00D8629E /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688437E1CAA37EF00D8629E /* Utilities.m */; }; +/* 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; }; + 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; }; + 76229A761CBB79E000B62CEF /* WindowWithStatusBarUnderlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowWithStatusBarUnderlay.h; sourceTree = ""; }; + 76229A771CBB79E000B62CEF /* WindowWithStatusBarUnderlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WindowWithStatusBarUnderlay.m; sourceTree = ""; }; + 767A5F101CAA3BFE004CDA8D /* tabBarIcons */ = {isa = PBXFileReference; lastKnownFileType = folder; path = tabBarIcons; sourceTree = ""; }; + 767A5F121CAA3C66004CDA8D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 768843511CAA37EF00D8629E /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = AppDelegate.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843521CAA37EF00D8629E /* CommentFeedModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = CommentFeedModel.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843531CAA37EF00D8629E /* CommentModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = CommentModel.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843541CAA37EF00D8629E /* CommentsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = CommentsNode.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843551CAA37EF00D8629E /* CommentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = CommentView.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843561CAA37EF00D8629E /* ImageURLModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ImageURLModel.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843591CAA37EF00D8629E /* LocationModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = LocationModel.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 7688435B1CAA37EF00D8629E /* PhotoCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PhotoCellNode.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 7688435C1CAA37EF00D8629E /* PhotoCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PhotoCollectionViewCell.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 7688435D1CAA37EF00D8629E /* PhotoFeedModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PhotoFeedModel.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 7688435E1CAA37EF00D8629E /* PhotoFeedNodeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PhotoFeedNodeController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843601CAA37EF00D8629E /* PhotoModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PhotoModel.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843611CAA37EF00D8629E /* PhotoTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PhotoTableViewCell.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843621CAA37EF00D8629E /* PhotoFeedViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PhotoFeedViewController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843631CAA37EF00D8629E /* UserModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = UserModel.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843661CAA37EF00D8629E /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Utilities.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 768843671CAA37EF00D8629E /* Sample.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Sample.pch; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 768843681CAA37EF00D8629E /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AppDelegate.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 768843691CAA37EF00D8629E /* CommentFeedModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = CommentFeedModel.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 7688436A1CAA37EF00D8629E /* CommentModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = CommentModel.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 7688436B1CAA37EF00D8629E /* CommentsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = CommentsNode.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 7688436C1CAA37EF00D8629E /* CommentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = CommentView.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 7688436D1CAA37EF00D8629E /* ImageURLModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ImageURLModel.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 768843701CAA37EF00D8629E /* LocationModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = LocationModel.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 768843711CAA37EF00D8629E /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = main.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 768843731CAA37EF00D8629E /* PhotoCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PhotoCellNode.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 768843741CAA37EF00D8629E /* PhotoCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PhotoCollectionViewCell.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 768843751CAA37EF00D8629E /* PhotoFeedModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PhotoFeedModel.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 768843761CAA37EF00D8629E /* PhotoFeedNodeController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PhotoFeedNodeController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 768843781CAA37EF00D8629E /* PhotoModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PhotoModel.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 768843791CAA37EF00D8629E /* PhotoTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PhotoTableViewCell.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 7688437A1CAA37EF00D8629E /* PhotoFeedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PhotoFeedViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 7688437B1CAA37EF00D8629E /* UserModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = UserModel.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 7688437E1CAA37EF00D8629E /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = Utilities.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 7688437F1CAA37EF00D8629E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 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 */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 768843511CAA37EF00D8629E /* AppDelegate.h */, + 768843681CAA37EF00D8629E /* AppDelegate.m */, + 76229A761CBB79E000B62CEF /* WindowWithStatusBarUnderlay.h */, + 76229A771CBB79E000B62CEF /* WindowWithStatusBarUnderlay.m */, + 767A5F141CAA3D8A004CDA8D /* Controller */, + 767A5F181CAA3DB0004CDA8D /* View */, + 767A5F171CAA3DA1004CDA8D /* Model */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 768843671CAA37EF00D8629E /* Sample.pch */, + 7688437F1CAA37EF00D8629E /* Info.plist */, + 768843711CAA37EF00D8629E /* main.m */, + 767A5F121CAA3C66004CDA8D /* Assets.xcassets */, + 767A5F101CAA3BFE004CDA8D /* tabBarIcons */, + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + ); + 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 = ""; + }; + 767A5F141CAA3D8A004CDA8D /* Controller */ = { + isa = PBXGroup; + children = ( + 767A5F161CAA3D96004CDA8D /* UIKit */, + 767A5F151CAA3D90004CDA8D /* ASDK */, + ); + name = Controller; + sourceTree = ""; + }; + 767A5F151CAA3D90004CDA8D /* ASDK */ = { + isa = PBXGroup; + children = ( + 7688435E1CAA37EF00D8629E /* PhotoFeedNodeController.h */, + 768843761CAA37EF00D8629E /* PhotoFeedNodeController.m */, + ); + name = ASDK; + sourceTree = ""; + }; + 767A5F161CAA3D96004CDA8D /* UIKit */ = { + isa = PBXGroup; + children = ( + 768843621CAA37EF00D8629E /* PhotoFeedViewController.h */, + 7688437A1CAA37EF00D8629E /* PhotoFeedViewController.m */, + ); + name = UIKit; + sourceTree = ""; + }; + 767A5F171CAA3DA1004CDA8D /* Model */ = { + isa = PBXGroup; + children = ( + 7688435D1CAA37EF00D8629E /* PhotoFeedModel.h */, + 768843751CAA37EF00D8629E /* PhotoFeedModel.m */, + 768843601CAA37EF00D8629E /* PhotoModel.h */, + 768843781CAA37EF00D8629E /* PhotoModel.m */, + 768843561CAA37EF00D8629E /* ImageURLModel.h */, + 7688436D1CAA37EF00D8629E /* ImageURLModel.m */, + 768843521CAA37EF00D8629E /* CommentFeedModel.h */, + 768843691CAA37EF00D8629E /* CommentFeedModel.m */, + 768843531CAA37EF00D8629E /* CommentModel.h */, + 7688436A1CAA37EF00D8629E /* CommentModel.m */, + 768843631CAA37EF00D8629E /* UserModel.h */, + 7688437B1CAA37EF00D8629E /* UserModel.m */, + 768843591CAA37EF00D8629E /* LocationModel.h */, + 768843701CAA37EF00D8629E /* LocationModel.m */, + 768843661CAA37EF00D8629E /* Utilities.h */, + 7688437E1CAA37EF00D8629E /* Utilities.m */, + ); + name = Model; + sourceTree = ""; + }; + 767A5F181CAA3DB0004CDA8D /* View */ = { + isa = PBXGroup; + children = ( + 767A5F191CAA3DB9004CDA8D /* UIKit */, + 767A5F1A1CAA3DBF004CDA8D /* ASDK */, + ); + name = View; + sourceTree = ""; + }; + 767A5F191CAA3DB9004CDA8D /* UIKit */ = { + isa = PBXGroup; + children = ( + 768843611CAA37EF00D8629E /* PhotoTableViewCell.h */, + 768843791CAA37EF00D8629E /* PhotoTableViewCell.m */, + 7688435C1CAA37EF00D8629E /* PhotoCollectionViewCell.h */, + 768843741CAA37EF00D8629E /* PhotoCollectionViewCell.m */, + 768843551CAA37EF00D8629E /* CommentView.h */, + 7688436C1CAA37EF00D8629E /* CommentView.m */, + ); + name = UIKit; + sourceTree = ""; + }; + 767A5F1A1CAA3DBF004CDA8D /* ASDK */ = { + isa = PBXGroup; + children = ( + 7688435B1CAA37EF00D8629E /* PhotoCellNode.h */, + 768843731CAA37EF00D8629E /* PhotoCellNode.m */, + 768843541CAA37EF00D8629E /* CommentsNode.h */, + 7688436B1CAA37EF00D8629E /* CommentsNode.m */, + ); + name = ASDK; + 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 */, + 06770D39D4186D6446B1BDD5 /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0730; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + DevelopmentTeam = X7PHB3A2FT; + }; + }; + }; + 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 */, + 767A5F111CAA3BFE004CDA8D /* tabBarIcons in Resources */, + 767A5F131CAA3C66004CDA8D /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 06770D39D4186D6446B1BDD5 /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 768843891CAA37EF00D8629E /* main.m in Sources */, + 7688438C1CAA37EF00D8629E /* PhotoCollectionViewCell.m in Sources */, + 768843921CAA37EF00D8629E /* PhotoFeedViewController.m in Sources */, + 76229A781CBB79E000B62CEF /* WindowWithStatusBarUnderlay.m in Sources */, + 768843821CAA37EF00D8629E /* CommentModel.m in Sources */, + 768843831CAA37EF00D8629E /* CommentsNode.m in Sources */, + 768843961CAA37EF00D8629E /* Utilities.m in Sources */, + 768843931CAA37EF00D8629E /* UserModel.m in Sources */, + 768843801CAA37EF00D8629E /* AppDelegate.m in Sources */, + 768843811CAA37EF00D8629E /* CommentFeedModel.m in Sources */, + 7688438E1CAA37EF00D8629E /* PhotoFeedNodeController.m in Sources */, + 768843841CAA37EF00D8629E /* CommentView.m in Sources */, + 768843881CAA37EF00D8629E /* LocationModel.m in Sources */, + 768843901CAA37EF00D8629E /* PhotoModel.m in Sources */, + 768843911CAA37EF00D8629E /* PhotoTableViewCell.m in Sources */, + 7688438B1CAA37EF00D8629E /* PhotoCellNode.m in Sources */, + 7688438D1CAA37EF00D8629E /* PhotoFeedModel.m in Sources */, + 768843851CAA37EF00D8629E /* ImageURLModel.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; + ENABLE_TESTABILITY = 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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Sample/Sample.pch; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Sample/Sample.pch; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples/ASDKgram/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/ASDKgram/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..d41d58c5d8 --- /dev/null +++ b/examples/ASDKgram/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/ASDKgram/Sample/AppDelegate.h b/examples/ASDKgram/Sample/AppDelegate.h new file mode 100644 index 0000000000..5c687c3f27 --- /dev/null +++ b/examples/ASDKgram/Sample/AppDelegate.h @@ -0,0 +1,16 @@ +// +// AppDelegate.h +// ASDKgram +// +// Created by Hannah Troisi on 2/16/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +@protocol PhotoFeedControllerProtocol +- (void)resetAllData; +@end + +@interface AppDelegate : UIResponder + +@end + diff --git a/examples/ASDKgram/Sample/AppDelegate.m b/examples/ASDKgram/Sample/AppDelegate.m new file mode 100644 index 0000000000..0796a1b00d --- /dev/null +++ b/examples/ASDKgram/Sample/AppDelegate.m @@ -0,0 +1,78 @@ +// +// AppDelegate.m +// ASDKgram +// +// Created by Hannah Troisi on 2/16/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "AppDelegate.h" +#import "PhotoFeedViewController.h" +#import "PhotoFeedNodeController.h" +#import "WindowWithStatusBarUnderlay.h" +#import "Utilities.h" + +@interface AppDelegate () +@end + +@implementation AppDelegate +{ + WindowWithStatusBarUnderlay *_window; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + // this UIWindow subclass is neccessary to make the status bar opaque + _window = [[WindowWithStatusBarUnderlay alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + _window.backgroundColor = [UIColor whiteColor]; + + // UIKit Home Feed viewController & navController + PhotoFeedNodeController *asdkHomeFeedVC = [[PhotoFeedNodeController alloc] init]; + UINavigationController *asdkHomeFeedNavCtrl = [[UINavigationController alloc] initWithRootViewController:asdkHomeFeedVC]; + asdkHomeFeedNavCtrl.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"ASDK" image:[UIImage imageNamed:@"home"] tag:0]; + asdkHomeFeedNavCtrl.hidesBarsOnSwipe = YES; + + // ASDK Home Feed viewController & navController + PhotoFeedViewController *uikitHomeFeedVC = [[PhotoFeedViewController alloc] init]; + UINavigationController *uikitHomeFeedNavCtrl = [[UINavigationController alloc] initWithRootViewController:uikitHomeFeedVC]; + uikitHomeFeedNavCtrl.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"UIKit" image:[UIImage imageNamed:@"home"] tag:0]; + uikitHomeFeedNavCtrl.hidesBarsOnSwipe = YES; + + // UITabBarController + UITabBarController *tabBarController = [[UITabBarController alloc] init]; + tabBarController.viewControllers = @[uikitHomeFeedNavCtrl, asdkHomeFeedNavCtrl]; + tabBarController.selectedViewController = asdkHomeFeedNavCtrl; + tabBarController.delegate = self; + [[UITabBar appearance] setTintColor:[UIColor darkBlueColor]]; + + _window.rootViewController = tabBarController; + [_window makeKeyAndVisible]; + + // Nav Bar appearance + NSDictionary *attributes = @{NSForegroundColorAttributeName:[UIColor whiteColor]}; + [[UINavigationBar appearance] setTitleTextAttributes:attributes]; + [[UINavigationBar appearance] setBarTintColor:[UIColor darkBlueColor]]; + [[UINavigationBar appearance] setTranslucent:NO]; + + // iOS8 hides the status bar in landscape orientation, this forces the status bar hidden status to NO + [application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone]; + [application setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone]; + + return YES; +} + +#pragma mark - UITabBarControllerDelegate + +- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController +{ + if ([viewController isKindOfClass:[UINavigationController class]]) { + NSArray *viewControllers = [(UINavigationController *)viewController viewControllers]; + UIViewController *rootViewController = viewControllers[0]; + if ([rootViewController conformsToProtocol:@protocol(PhotoFeedControllerProtocol)]) { + // FIXME: the dataModel does not currently handle clearing data during loading properly +// [(id )rootViewController resetAllData]; + } + } +} + +@end diff --git a/examples/ASDKgram/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/ASDKgram/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..eeea76c2db --- /dev/null +++ b/examples/ASDKgram/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,73 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Contents.json b/examples/ASDKgram/Sample/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/examples/ASDKgram/Sample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/Contents.json b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/Contents.json b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/Contents.json new file mode 100644 index 0000000000..07252697c8 --- /dev/null +++ b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "camera.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "camera@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/camera.png b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/camera.png new file mode 100644 index 0000000000..2eeecba825 Binary files /dev/null and b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/camera.png differ diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/camera@2x.png b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/camera@2x.png new file mode 100644 index 0000000000..c1ea4ab857 Binary files /dev/null and b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/camera@2x.png differ diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/crosshairs.imageset/Contents.json b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/crosshairs.imageset/Contents.json new file mode 100644 index 0000000000..66e65dc03e --- /dev/null +++ b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/crosshairs.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "crosshair.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/crosshairs.imageset/crosshair.png b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/crosshairs.imageset/crosshair.png new file mode 100644 index 0000000000..ea3c5e27ba Binary files /dev/null and b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/crosshairs.imageset/crosshair.png differ diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/Contents.json b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/Contents.json new file mode 100644 index 0000000000..37e4afe0e2 --- /dev/null +++ b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "earth.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "earth@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/earth.png b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/earth.png new file mode 100644 index 0000000000..c182ea5565 Binary files /dev/null and b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/earth.png differ diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/earth@2x.png b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/earth@2x.png new file mode 100644 index 0000000000..b8049a5004 Binary files /dev/null and b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/earth@2x.png differ diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/Contents.json b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/Contents.json new file mode 100644 index 0000000000..ce48b1b641 --- /dev/null +++ b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "home.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "home@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/home.png b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/home.png new file mode 100644 index 0000000000..b88cd66a4b Binary files /dev/null and b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/home.png differ diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/home@2x.png b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/home@2x.png new file mode 100644 index 0000000000..838e660097 Binary files /dev/null and b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/home@2x.png differ diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/Contents.json b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/Contents.json new file mode 100644 index 0000000000..ecb5bbebcf --- /dev/null +++ b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "profile.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "profile@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/profile.png b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/profile.png new file mode 100644 index 0000000000..d885b3aedf Binary files /dev/null and b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/profile.png differ diff --git a/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/profile@2x.png b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/profile@2x.png new file mode 100644 index 0000000000..81352fe0cb Binary files /dev/null and b/examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/profile@2x.png differ diff --git a/examples/ASDKgram/Sample/Base.lproj/LaunchScreen.storyboard b/examples/ASDKgram/Sample/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..8326657f7a --- /dev/null +++ b/examples/ASDKgram/Sample/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASDKgram/Sample/CommentFeedModel.h b/examples/ASDKgram/Sample/CommentFeedModel.h new file mode 100644 index 0000000000..adfd4206c2 --- /dev/null +++ b/examples/ASDKgram/Sample/CommentFeedModel.h @@ -0,0 +1,26 @@ +// +// CommentFeedModel.h +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "CommentModel.h" + +@interface CommentFeedModel : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithPhotoID:(NSString *)photoID NS_DESIGNATED_INITIALIZER; + +- (NSUInteger)numberOfItemsInFeed; +- (CommentModel *)objectAtIndex:(NSUInteger)index; + +- (NSUInteger)numberOfCommentsForPhoto; +- (BOOL)numberOfCommentsForPhotoExceedsInteger:(NSUInteger)number; +- (NSAttributedString *)viewAllCommentsAttributedString; + +- (void)requestPageWithCompletionBlock:(void (^)(NSArray *))block; +- (void)refreshFeedWithCompletionBlock:(void (^)(NSArray *))block; + +@end diff --git a/examples/ASDKgram/Sample/CommentFeedModel.m b/examples/ASDKgram/Sample/CommentFeedModel.m new file mode 100644 index 0000000000..257154f6f2 --- /dev/null +++ b/examples/ASDKgram/Sample/CommentFeedModel.m @@ -0,0 +1,195 @@ +// +// CommentFeedModel.m +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "CommentFeedModel.h" +#import "Utilities.h" + +#define NUM_COMMENTS_TO_SHOW 3 + +#define fiveHundredPX_ENDPOINT_HOST @"https://api.500px.com/v1/" +#define fiveHundredPX_ENDPOINT_COMMENTS @"photos/4928401/comments" +#define fiveHundredPX_ENDPOINT_SEARCH @"photos/search?geo=" //latitude,longitude,radius +#define fiveHundredPX_ENDPOINT_USER @"photos?user_id=" +#define fiveHundredPX_CONSUMER_KEY_PARAM @"&consumer_key=Fi13GVb8g53sGvHICzlram7QkKOlSDmAmp9s9aqC" + +@implementation CommentFeedModel +{ + NSMutableArray *_comments; // array of CommentModel objects + + NSString *_photoID; + NSString *_urlString; + NSUInteger _currentPage; + NSUInteger _totalPages; + NSUInteger _totalItems; + + BOOL _fetchPageInProgress; + BOOL _refreshFeedInProgress; +} + +#pragma mark - Properties + +- (NSMutableArray *)comments +{ + return _comments; +} + +#pragma mark - Lifecycle + +- (instancetype)initWithPhotoID:(NSString *)photoID +{ + self = [super init]; + + if (self) { + _photoID = photoID; + _currentPage = 0; + _totalPages = 0; + _totalItems = 0; + _comments = [[NSMutableArray alloc] init]; + _urlString = [NSString stringWithFormat:@"https://api.500px.com/v1/photos/%@/comments?",photoID]; + } + + return self; +} + + +#pragma mark - Instance Methods + +- (NSUInteger)numberOfItemsInFeed +{ + return [_comments count]; +} + +- (CommentModel *)objectAtIndex:(NSUInteger)index +{ + return [_comments objectAtIndex:index]; +} + +- (NSUInteger)numberOfCommentsForPhoto +{ + return _totalItems; +} + +- (BOOL)numberOfCommentsForPhotoExceedsInteger:(NSUInteger)number +{ + return (_totalItems > number); +} + +- (NSAttributedString *)viewAllCommentsAttributedString +{ + NSString *string = [NSString stringWithFormat:@"View all %@ comments", [NSNumber numberWithUnsignedInteger:_totalItems]]; + NSAttributedString *attrString = [NSAttributedString attributedStringWithString:string fontSize:14 color:[UIColor lightGrayColor] firstWordColor:nil]; + return attrString; +} + +- (void)requestPageWithCompletionBlock:(void (^)(NSArray *))block +{ + // only one fetch at a time + if (_fetchPageInProgress) { + return; + } else { + _fetchPageInProgress = YES; + [self fetchPageWithCompletionBlock:block]; + } +} + +- (void)refreshFeedWithCompletionBlock:(void (^)(NSArray *))block +{ + // only one fetch at a time + if (_refreshFeedInProgress) { + return; + } else { + _refreshFeedInProgress = YES; + _currentPage = 0; + + // FIXME: blow away any other requests in progress + + [self fetchPageWithCompletionBlock:^(NSArray *newPhotos) { + if (block) { + block(newPhotos); + } + _refreshFeedInProgress = NO; + } replaceData:YES]; + } +} + +#pragma mark - Helper Methods +- (void)fetchPageWithCompletionBlock:(void (^)(NSArray *))block +{ + [self fetchPageWithCompletionBlock:block replaceData:NO]; +} + +- (void)fetchPageWithCompletionBlock:(void (^)(NSArray *))block replaceData:(BOOL)replaceData +{ + // early return if reached end of pages + if (_totalPages) { + if (_currentPage == _totalPages) { + return; + } + } + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + NSMutableArray *newComments = [NSMutableArray array]; + + NSUInteger nextPage = _currentPage + 1; + + NSString *urlAdditions = [NSString stringWithFormat:@"page=%lu", (unsigned long)nextPage]; + NSURL *url = [NSURL URLWithString:[_urlString stringByAppendingString:urlAdditions]]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; + NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + + if (data) { + + NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; + + if ([response isKindOfClass:[NSDictionary class]]) { + + _currentPage = [[response valueForKeyPath:@"current_page"] integerValue]; + _totalPages = [[response valueForKeyPath:@"total_pages"] integerValue]; + _totalItems = [[response valueForKeyPath:@"total_items"] integerValue]; + + NSArray *comments = [response valueForKeyPath:@"comments"]; + + if ([comments isKindOfClass:[NSArray class]]) { + + NSUInteger numComments = [comments count]; + if (numComments > NUM_COMMENTS_TO_SHOW) { + comments = [comments subarrayWithRange:(NSRange){numComments-NUM_COMMENTS_TO_SHOW, NUM_COMMENTS_TO_SHOW}]; + } + + for (NSDictionary *commentDictionary in comments) { + + if ([response isKindOfClass:[NSDictionary class]]) { + + CommentModel *comment = [[CommentModel alloc] initWithDictionary:commentDictionary]; + + if (comment) { + [newComments addObject:comment]; + } + } + } + } + } + } + dispatch_async(dispatch_get_main_queue(), ^{ + _fetchPageInProgress = NO; + if (replaceData) { + _comments = [newComments mutableCopy]; + } else { + [_comments addObjectsFromArray:newComments]; + } + if (block) { + block(newComments); + } + }); + }]; + [task resume]; + }); +} + +@end diff --git a/examples/ASDKgram/Sample/CommentModel.h b/examples/ASDKgram/Sample/CommentModel.h new file mode 100644 index 0000000000..f102cfb008 --- /dev/null +++ b/examples/ASDKgram/Sample/CommentModel.h @@ -0,0 +1,24 @@ +// +// CommentModel.h +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +@interface CommentModel : NSObject + +@property (nonatomic, assign, readonly) NSUInteger ID; +@property (nonatomic, assign, readonly) NSUInteger commenterID; +@property (nonatomic, strong, readonly) NSString *commenterUsername; +@property (nonatomic, strong, readonly) NSString *commenterAvatarURL; +@property (nonatomic, strong, readonly) NSString *body; +@property (nonatomic, strong, readonly) NSString *uploadDateString; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDictionary:(NSDictionary *)photoDictionary NS_DESIGNATED_INITIALIZER; + +- (NSAttributedString *)commentAttributedString; +- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size; + +@end diff --git a/examples/ASDKgram/Sample/CommentModel.m b/examples/ASDKgram/Sample/CommentModel.m new file mode 100644 index 0000000000..7b9a41d675 --- /dev/null +++ b/examples/ASDKgram/Sample/CommentModel.m @@ -0,0 +1,51 @@ +// +// CommentModel.m +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "CommentModel.h" +#import "Utilities.h" + +@implementation CommentModel +{ + NSDictionary *_dictionaryRepresentation; + NSString *_uploadDateRaw; +} + +#pragma mark - Lifecycle + +- (instancetype)initWithDictionary:(NSDictionary *)photoDictionary +{ + self = [super init]; + + if (self) { + _dictionaryRepresentation = photoDictionary; + _ID = [[photoDictionary objectForKey:@"id"] integerValue]; + _commenterID = [[photoDictionary objectForKey:@"user_id"] integerValue]; + _commenterUsername = [photoDictionary valueForKeyPath:@"user.username"]; + _commenterAvatarURL = [photoDictionary valueForKeyPath:@"user.userpic_url"]; + _body = [photoDictionary objectForKey:@"body"]; + _uploadDateRaw = [photoDictionary valueForKeyPath:@"created_at"]; + _uploadDateString = [NSString elapsedTimeStringSinceDate:_uploadDateRaw]; + } + + return self; +} + +#pragma mark - Instance Methods + +- (NSAttributedString *)commentAttributedString +{ + NSString *commentString = [NSString stringWithFormat:@"%@ %@",[_commenterUsername lowercaseString], _body]; + return [NSAttributedString attributedStringWithString:commentString fontSize:14 color:[UIColor darkGrayColor] firstWordColor:[UIColor darkBlueColor]]; +} + +- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size; +{ + return [NSAttributedString attributedStringWithString:self.uploadDateString fontSize:size color:[UIColor lightGrayColor] firstWordColor:nil]; +} + +@end diff --git a/examples/ASDKgram/Sample/CommentView.h b/examples/ASDKgram/Sample/CommentView.h new file mode 100644 index 0000000000..c17b2a402d --- /dev/null +++ b/examples/ASDKgram/Sample/CommentView.h @@ -0,0 +1,17 @@ +// +// CommentView.h +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "CommentFeedModel.h" + +@interface CommentView : UIView + ++ (CGFloat)heightForCommentFeedModel:(CommentFeedModel *)feed withWidth:(CGFloat)width; + +- (void)updateWithCommentFeedModel:(CommentFeedModel *)feed; + +@end diff --git a/examples/ASDKgram/Sample/CommentView.m b/examples/ASDKgram/Sample/CommentView.m new file mode 100644 index 0000000000..bf82e70f05 --- /dev/null +++ b/examples/ASDKgram/Sample/CommentView.m @@ -0,0 +1,138 @@ +// +// CommentView.m +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "CommentView.h" +#import "PhotoFeedModel.h" +#import "Utilities.h" + +#define INTER_COMMENT_SPACING 5 +#define NUM_COMMENTS_TO_SHOW 3 + +@implementation CommentView +{ + CommentFeedModel *_commentFeed; + NSMutableArray *_commentLabels; +} + +#pragma mark - Class Methods + ++ (CGFloat)heightForCommentFeedModel:(CommentFeedModel *)feed withWidth:(CGFloat)width +{ + NSAttributedString *string; + CGRect rect; + CGFloat height = 0; + + BOOL addViewAllCommentsLabel = [feed numberOfCommentsForPhotoExceedsInteger:NUM_COMMENTS_TO_SHOW]; + if (addViewAllCommentsLabel) { + string = [feed viewAllCommentsAttributedString]; + rect = [string boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) + options:NSStringDrawingUsesLineFragmentOrigin + context:nil]; + height += rect.size.height; + } + + NSUInteger numCommentsInFeed = [feed numberOfItemsInFeed]; + + for (int i = 0; i < numCommentsInFeed; i++) { + + string = [[feed objectAtIndex:i] commentAttributedString]; + rect = [string boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) + options:NSStringDrawingUsesLineFragmentOrigin + context:nil]; + height += rect.size.height + INTER_COMMENT_SPACING; + } + + return roundf(height); +} + +#pragma mark - Lifecycle + +- (instancetype)init +{ + self = [super init]; + if (self) { + _commentLabels = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + CGSize boundsSize = self.bounds.size; + CGRect rect = CGRectMake(0, 0, boundsSize.width, -INTER_COMMENT_SPACING); + + for (UILabel *commentsLabel in _commentLabels) { + rect.origin.y += rect.size.height + INTER_COMMENT_SPACING; + rect.size = [commentsLabel sizeThatFits:CGSizeMake(boundsSize.width, CGFLOAT_MAX)]; + commentsLabel.frame = rect; + } +} + +#pragma mark - Instance Methods + +- (void)updateWithCommentFeedModel:(CommentFeedModel *)feed +{ + _commentFeed = feed; + [self removeCommentLabels]; + + if (_commentFeed) { + [self createCommentLabels]; + + BOOL addViewAllCommentsLabel = [feed numberOfCommentsForPhotoExceedsInteger:NUM_COMMENTS_TO_SHOW]; + NSAttributedString *commentLabelString; + int labelsIndex = 0; + + if (addViewAllCommentsLabel) { + commentLabelString = [_commentFeed viewAllCommentsAttributedString]; + [[_commentLabels objectAtIndex:labelsIndex] setAttributedText:commentLabelString]; + labelsIndex++; + } + + NSUInteger numCommentsInFeed = [_commentFeed numberOfItemsInFeed]; + + for (int feedIndex = 0; feedIndex < numCommentsInFeed; feedIndex++) { + commentLabelString = [[_commentFeed objectAtIndex:feedIndex] commentAttributedString]; + [[_commentLabels objectAtIndex:labelsIndex] setAttributedText:commentLabelString]; + labelsIndex++; + } + + [self setNeedsLayout]; + } +} + +#pragma mark - Helper Methods + +- (void)removeCommentLabels +{ + for (UILabel *commentLabel in _commentLabels) { + [commentLabel removeFromSuperview]; + } + + [_commentLabels removeAllObjects]; +} + +- (void)createCommentLabels +{ + BOOL addViewAllCommentsLabel = [_commentFeed numberOfCommentsForPhotoExceedsInteger:NUM_COMMENTS_TO_SHOW]; + NSUInteger numCommentsInFeed = [_commentFeed numberOfItemsInFeed]; + + NSUInteger numLabelsToAdd = (addViewAllCommentsLabel) ? numCommentsInFeed + 1 : numCommentsInFeed; + + for (NSUInteger i = 0; i < numLabelsToAdd; i++) { + + UILabel *commentLabel = [[UILabel alloc] init]; + commentLabel.numberOfLines = 3; + + [_commentLabels addObject:commentLabel]; + [self addSubview:commentLabel]; + } +} + +@end diff --git a/examples/ASDKgram/Sample/CommentsNode.h b/examples/ASDKgram/Sample/CommentsNode.h new file mode 100644 index 0000000000..62ebd92ead --- /dev/null +++ b/examples/ASDKgram/Sample/CommentsNode.h @@ -0,0 +1,16 @@ +// +// CommentsNode.h +// ASDKgram +// +// Created by Hannah Troisi on 3/21/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import +#import "CommentFeedModel.h" + +@interface CommentsNode : ASTextCellNode + +- (void)updateWithCommentFeedModel:(CommentFeedModel *)feed; + +@end diff --git a/examples/ASDKgram/Sample/CommentsNode.m b/examples/ASDKgram/Sample/CommentsNode.m new file mode 100644 index 0000000000..86d164eae8 --- /dev/null +++ b/examples/ASDKgram/Sample/CommentsNode.m @@ -0,0 +1,101 @@ +// +// CommentsNode.m +// ASDKgram +// +// Created by Hannah Troisi on 3/21/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "CommentsNode.h" + +#define INTER_COMMENT_SPACING 5 +#define NUM_COMMENTS_TO_SHOW 3 + +@implementation CommentsNode +{ + CommentFeedModel *_commentFeed; + NSMutableArray *_commentNodes; +} + +#pragma mark - Lifecycle + +- (instancetype)init +{ + self = [super init]; + if (self) { + _commentNodes = [[NSMutableArray alloc] init]; + } + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStack.spacing = INTER_COMMENT_SPACING; + [verticalStack setChildren:_commentNodes]; + + return verticalStack; +} + +#pragma mark - Instance Methods + +- (void)updateWithCommentFeedModel:(CommentFeedModel *)feed +{ + _commentFeed = feed; + [self removeCommentLabels]; + + if (_commentFeed) { + [self createCommentLabels]; + + BOOL addViewAllCommentsLabel = [feed numberOfCommentsForPhotoExceedsInteger:NUM_COMMENTS_TO_SHOW]; + NSAttributedString *commentLabelString; + int labelsIndex = 0; + + if (addViewAllCommentsLabel) { + commentLabelString = [_commentFeed viewAllCommentsAttributedString]; + [[_commentNodes objectAtIndex:labelsIndex] setAttributedString:commentLabelString]; + labelsIndex++; + } + + NSUInteger numCommentsInFeed = [_commentFeed numberOfItemsInFeed]; + + for (int feedIndex = 0; feedIndex < numCommentsInFeed; feedIndex++) { + commentLabelString = [[_commentFeed objectAtIndex:feedIndex] commentAttributedString]; + [[_commentNodes objectAtIndex:labelsIndex] setAttributedString:commentLabelString]; + labelsIndex++; + } + + [self setNeedsLayout]; + } +} + + +#pragma mark - Helper Methods + +- (void)removeCommentLabels +{ + for (ASTextNode *commentLabel in _commentNodes) { + [commentLabel removeFromSupernode]; + } + + [_commentNodes removeAllObjects]; +} + +- (void)createCommentLabels +{ + BOOL addViewAllCommentsLabel = [_commentFeed numberOfCommentsForPhotoExceedsInteger:NUM_COMMENTS_TO_SHOW]; + NSUInteger numCommentsInFeed = [_commentFeed numberOfItemsInFeed]; + + NSUInteger numLabelsToAdd = (addViewAllCommentsLabel) ? numCommentsInFeed + 1 : numCommentsInFeed; + + for (NSUInteger i = 0; i < numLabelsToAdd; i++) { + + ASTextNode *commentLabel = [[ASTextNode alloc] init]; + commentLabel.maximumNumberOfLines = 3; + + [_commentNodes addObject:commentLabel]; + [self addSubnode:commentLabel]; + } +} + +@end diff --git a/examples/ASDKgram/Sample/ImageURLModel.h b/examples/ASDKgram/Sample/ImageURLModel.h new file mode 100644 index 0000000000..a0a8296680 --- /dev/null +++ b/examples/ASDKgram/Sample/ImageURLModel.h @@ -0,0 +1,13 @@ +// +// ImageURLModel.h +// ASDKgram +// +// Created by Hannah Troisi on 3/11/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +@interface ImageURLModel : NSObject + ++ (NSString *)imageParameterForClosestImageSize:(CGSize)size; + +@end diff --git a/examples/ASDKgram/Sample/ImageURLModel.m b/examples/ASDKgram/Sample/ImageURLModel.m new file mode 100644 index 0000000000..18cd6729ad --- /dev/null +++ b/examples/ASDKgram/Sample/ImageURLModel.m @@ -0,0 +1,49 @@ +// +// ImageURLModel.m +// ASDKgram +// +// Created by Hannah Troisi on 3/11/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "ImageURLModel.h" + +@implementation ImageURLModel + ++ (NSString *)imageParameterForClosestImageSize:(CGSize)size +{ + BOOL squareImageRequested = (size.width == size.height) ? YES : NO; + + NSUInteger imageParameterID; + if (squareImageRequested) { + imageParameterID = [self imageParameterForSquareCroppedSize:size]; + } + + return [NSString stringWithFormat:@"&image_size=%lu", (long)imageParameterID]; +} + +// 500px standard cropped image sizes ++ (NSUInteger)imageParameterForSquareCroppedSize:(CGSize)size +{ + NSUInteger imageParameterID; + + if (size.height <= 70) { + imageParameterID = 1; + } else if (size.height <= 100) { + imageParameterID = 100; + } else if (size.height <= 140) { + imageParameterID = 2; + } else if (size.height <= 200) { + imageParameterID = 200; + } else if (size.height <= 280) { + imageParameterID = 3; + } else if (size.height <= 400) { + imageParameterID = 400; + } else { + imageParameterID = 600; + } + + return imageParameterID; +} + +@end diff --git a/examples/ASDKgram/Sample/Info.plist b/examples/ASDKgram/Sample/Info.plist new file mode 100644 index 0000000000..22adc008a5 --- /dev/null +++ b/examples/ASDKgram/Sample/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSLocationWhenInUseUsageDescription + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/examples/ASDKgram/Sample/LocationModel.h b/examples/ASDKgram/Sample/LocationModel.h new file mode 100644 index 0000000000..cdc7cbd7a1 --- /dev/null +++ b/examples/ASDKgram/Sample/LocationModel.h @@ -0,0 +1,22 @@ +// +// LocationModel.h +// ASDKgram +// +// Created by Hannah Troisi on 2/26/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "CoreLocation/CoreLocation.h" + +@interface LocationModel : NSObject + +@property (nonatomic, assign, readonly) CLLocationCoordinate2D coordinates; +@property (nonatomic, strong, readonly) CLPlacemark *placemark; +@property (nonatomic, strong, readonly) NSString *locationString; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWith500pxPhoto:(NSDictionary *)dictionary NS_DESIGNATED_INITIALIZER; + +- (void)reverseGeocodedLocationWithCompletionBlock:(void (^)(LocationModel *))blockName; + +@end diff --git a/examples/ASDKgram/Sample/LocationModel.m b/examples/ASDKgram/Sample/LocationModel.m new file mode 100644 index 0000000000..ed1fc28449 --- /dev/null +++ b/examples/ASDKgram/Sample/LocationModel.m @@ -0,0 +1,135 @@ +// +// LocationModel.m +// ASDKgram +// +// Created by Hannah Troisi on 2/26/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "LocationModel.h" +#import + +@implementation LocationModel +{ + BOOL _placemarkFetchInProgress; + void (^_placemarkCallbackBlock)(LocationModel *); +} + +#pragma mark - Lifecycle + +- (nullable instancetype)initWith500pxPhoto:(NSDictionary *)dictionary +{ + NSNumber *latitude = [dictionary objectForKey:@"latitude"]; + NSNumber *longitude = [dictionary objectForKey:@"longitude"]; + + // early return if location is "" + if (![latitude isKindOfClass:[NSNumber class]] || ![longitude isKindOfClass:[NSNumber class]]) { + return nil; + } + + self = [super init]; + + if (self) { + // set coordiantes + _coordinates = CLLocationCoordinate2DMake([latitude floatValue], [longitude floatValue]); + + // get CLPlacemark with MKReverseGeocoder + [self beginReverseGeocodingLocationFromCoordinates]; + } + + return self; +} + +#pragma mark - Instance Methods + +// return location placemark if fetched, else set completion block for fetch finish +- (void)reverseGeocodedLocationWithCompletionBlock:(void (^)(LocationModel *))blockName +{ + if (_placemark) { + + // call block if placemark already fetched + if (blockName) { + blockName(self); + } + + } else { + + // set placemark reverse geocoding completion block + _placemarkCallbackBlock = blockName; + + // if fetch not in progress, begin + if (!_placemarkFetchInProgress) { + + [self beginReverseGeocodingLocationFromCoordinates]; + } + } +} + + +#pragma mark - Helper Methods + +- (void)beginReverseGeocodingLocationFromCoordinates +{ + if (_placemarkFetchInProgress) { + return; + } + _placemarkFetchInProgress = YES; + + CLLocation *location = [[CLLocation alloc] initWithLatitude:_coordinates.latitude longitude:_coordinates.longitude]; + CLGeocoder *geocoder = [[CLGeocoder alloc] init]; + + [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray * _Nullable placemarks, NSError * _Nullable error) { + + // completion handler gets called on main thread + _placemark = [placemarks lastObject]; + _locationString = [self locationStringFromCLPlacemark]; + + // check if completion block set, call it - DO NOT CALL A NIL BLOCK! + if (_placemarkCallbackBlock) { + + // call the block with arguments + _placemarkCallbackBlock(self); + } + }]; +} + +- (nullable NSString *)locationStringFromCLPlacemark +{ + // early return if no location info + if (!_placemark) + { + return nil; + } + +// @property (nonatomic, readonly, copy, nullable) NSString *name; // eg. Apple Inc. +// @property (nonatomic, readonly, copy, nullable) NSString *thoroughfare; // street name, eg. Infinite Loop +// @property (nonatomic, readonly, copy, nullable) NSString *subThoroughfare; // eg. 1 +// @property (nonatomic, readonly, copy, nullable) NSString *locality; // city, eg. Cupertino +// @property (nonatomic, readonly, copy, nullable) NSString *subLocality; // neighborhood, common name, eg. Mission District +// @property (nonatomic, readonly, copy, nullable) NSString *administrativeArea; // state, eg. CA +// @property (nonatomic, readonly, copy, nullable) NSString *subAdministrativeArea; // county, eg. Santa Clara +// @property (nonatomic, readonly, copy, nullable) NSString *postalCode; // zip code, eg. 95014 +// @property (nonatomic, readonly, copy, nullable) NSString *ISOcountryCode; // eg. US +// @property (nonatomic, readonly, copy, nullable) NSString *country; // eg. United States +// @property (nonatomic, readonly, copy, nullable) NSString *inlandWater; // eg. Lake Tahoe +// @property (nonatomic, readonly, copy, nullable) NSString *ocean; // eg. Pacific Ocean +// @property (nonatomic, readonly, copy, nullable) NSArray *areasOfInterest; // eg. Golden Gate Park + + NSString *locationString; + + if (_placemark.inlandWater) { + locationString = _placemark.inlandWater; + } else if (_placemark.subLocality && _placemark.locality) { + locationString = [NSString stringWithFormat:@"%@, %@", _placemark.subLocality, _placemark.locality]; + } else if (_placemark.administrativeArea && _placemark.subAdministrativeArea) { + locationString = [NSString stringWithFormat:@"%@, %@", _placemark.subAdministrativeArea, _placemark.administrativeArea]; + } else if (_placemark.country) { + locationString = _placemark.country; + } else { + locationString = @"ERROR"; + } + + return locationString; +} + +@end diff --git a/examples/ASDKgram/Sample/PhotoCellNode.h b/examples/ASDKgram/Sample/PhotoCellNode.h new file mode 100644 index 0000000000..058fd812c3 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoCellNode.h @@ -0,0 +1,19 @@ +// +// PhotoCellNode.h +// Flickrgram +// +// Created by Hannah Troisi on 2/17/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import +#import "PhotoModel.h" +#import +#import "PhotoTableViewCell.h" // PhotoTableViewCellProtocol + + +@interface PhotoCellNode : ASCellNode + +- (instancetype)initWithPhotoObject:(PhotoModel *)photo; + +@end diff --git a/examples/ASDKgram/Sample/PhotoCellNode.m b/examples/ASDKgram/Sample/PhotoCellNode.m new file mode 100644 index 0000000000..6c030a2917 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoCellNode.m @@ -0,0 +1,187 @@ +// +// PhotoCellNode.m +// ASDKgram +// +// Created by Hannah Troisi on 2/17/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "PhotoCellNode.h" +#import "Utilities.h" +#import "AsyncDisplayKit.h" +#import "ASDisplayNode+Beta.h" +#import "CommentsNode.h" +#import "PINImageView+PINRemoteImage.h" +#import "PINButton+PINRemoteImage.h" + +#define DEBUG_PHOTOCELL_LAYOUT 0 + +#define HEADER_HEIGHT 50 +#define USER_IMAGE_HEIGHT 30 +#define HORIZONTAL_BUFFER 10 +#define VERTICAL_BUFFER 5 +#define FONT_SIZE 14 + +@implementation PhotoCellNode +{ + PhotoModel *_photoModel; + CommentsNode *_photoCommentsView; + ASNetworkImageNode *_userAvatarImageView; + ASNetworkImageNode *_photoImageView; + ASTextNode *_userNameLabel; + ASTextNode *_photoLocationLabel; + ASTextNode *_photoTimeIntervalSincePostLabel; + ASTextNode *_photoLikesLabel; + ASTextNode *_photoDescriptionLabel; +} + +#pragma mark - Lifecycle + +- (instancetype)initWithPhotoObject:(PhotoModel *)photo; +{ + self = [super init]; + + if (self) { + + _photoModel = photo; + + _userAvatarImageView = [[ASNetworkImageNode alloc] init]; + _userAvatarImageView.URL = photo.ownerUserProfile.userPicURL; // FIXME: make round + + // FIXME: autocomplete for this line seems broken + [_userAvatarImageView setImageModificationBlock:^UIImage *(UIImage *image) { + CGSize profileImageSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); + return [image makeCircularImageWithSize:profileImageSize]; + }]; + + _photoImageView = [[ASNetworkImageNode alloc] init]; + _photoImageView.URL = photo.URL; + _photoImageView.layerBacked = YES; + + _userNameLabel = [[ASTextNode alloc] init]; + _userNameLabel.attributedString = [photo.ownerUserProfile usernameAttributedStringWithFontSize:FONT_SIZE]; + + _photoLocationLabel = [[ASTextNode alloc] init]; + _photoLocationLabel.maximumNumberOfLines = 1; + [photo.location reverseGeocodedLocationWithCompletionBlock:^(LocationModel *locationModel) { + + // check and make sure this is still relevant for this cell (and not an old cell) + // make sure to use _photoModel instance variable as photo may change when cell is reused, + // where as local variable will never change + if (locationModel == _photoModel.location) { + _photoLocationLabel.attributedString = [photo locationAttributedStringWithFontSize:FONT_SIZE]; + [self setNeedsLayout]; + } + }]; + + _photoTimeIntervalSincePostLabel = [self createLayerBackedTextNodeWithString:[photo uploadDateAttributedStringWithFontSize:FONT_SIZE]]; + _photoLikesLabel = [self createLayerBackedTextNodeWithString:[photo likesAttributedStringWithFontSize:FONT_SIZE]]; + _photoDescriptionLabel = [self createLayerBackedTextNodeWithString:[photo descriptionAttributedStringWithFontSize:FONT_SIZE]]; + _photoDescriptionLabel.maximumNumberOfLines = 3; + + _photoCommentsView = [[CommentsNode alloc] init]; + _photoCommentsView.shouldRasterizeDescendants = YES; + + // instead of adding everything addSubnode: + self.usesImplicitHierarchyManagement = YES; + +#if DEBUG_PHOTOCELL_LAYOUT + _userAvatarImageView.backgroundColor = [UIColor greenColor]; + _userNameLabel.backgroundColor = [UIColor greenColor]; + _photoLocationLabel.backgroundColor = [UIColor greenColor]; + _photoTimeIntervalSincePostLabel.backgroundColor = [UIColor greenColor]; + _photoCommentsView.backgroundColor = [UIColor greenColor]; + _photoDescriptionLabel.backgroundColor = [UIColor greenColor]; + _photoLikesLabel.backgroundColor = [UIColor greenColor]; +#endif + } + + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + // username / photo location header vertical stack + _photoLocationLabel.flexShrink = YES; + _userNameLabel.flexShrink = YES; + + ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + headerSubStack.flexShrink = YES; + if (_photoLocationLabel.attributedString) { + [headerSubStack setChildren:@[_userNameLabel, _photoLocationLabel]]; + } else { + [headerSubStack setChildren:@[_userNameLabel]]; + } + + // header stack + + _userAvatarImageView.preferredFrameSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); // constrain avatar image frame size + _photoTimeIntervalSincePostLabel.spacingBefore = HORIZONTAL_BUFFER; // to remove double spaces around spacer + + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; // FIXME: long locations overflow post time - set max size? + spacer.flexGrow = YES; + + UIEdgeInsets avatarInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *avatarInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:avatarInsets child:_userAvatarImageView]; + + ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; + headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack + headerStack.justifyContent = ASStackLayoutJustifyContentStart; // justify content to the left side of the header stack + [headerStack setChildren:@[avatarInset, headerSubStack, spacer, _photoTimeIntervalSincePostLabel]]; + + // header inset stack + UIEdgeInsets insets = UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *headerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:headerStack]; + + // footer stack + ASStackLayoutSpec *footerStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + footerStack.spacing = VERTICAL_BUFFER; + [footerStack setChildren:@[_photoLikesLabel, _photoDescriptionLabel, _photoCommentsView]]; + + // footer inset stack + UIEdgeInsets footerInsets = UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *footerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:footerInsets child:footerStack]; + + // vertical stack + CGFloat cellWidth = constrainedSize.max.width; + _photoImageView.preferredFrameSize = CGSizeMake(cellWidth, cellWidth); // constrain photo frame size + + ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space + [verticalStack setChildren:@[headerWithInset, _photoImageView, footerWithInset]]; + + return verticalStack; +} + +#pragma mark - Instance Methods + +- (void)fetchData +{ + [super fetchData]; + + [_photoModel.commentFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) { + [self loadCommentsForPhoto:_photoModel]; + }]; +} + +#pragma mark - Helper Methods + +- (ASTextNode *)createLayerBackedTextNodeWithString:(NSAttributedString *)attributedString +{ + ASTextNode *textNode = [[ASTextNode alloc] init]; + textNode.layerBacked = YES; + textNode.attributedString = attributedString; + + return textNode; +} + +- (void)loadCommentsForPhoto:(PhotoModel *)photo +{ + if (photo.commentFeed.numberOfItemsInFeed > 0) { + [_photoCommentsView updateWithCommentFeedModel:photo.commentFeed]; + + [self setNeedsLayout]; + } +} + +@end diff --git a/examples/ASDKgram/Sample/PhotoCollectionViewCell.h b/examples/ASDKgram/Sample/PhotoCollectionViewCell.h new file mode 100644 index 0000000000..2a3d5268c4 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoCollectionViewCell.h @@ -0,0 +1,15 @@ +// +// PhotoCollectionViewCell.h +// ASDKgram +// +// Created by Hannah Troisi on 3/2/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "PhotoModel.h" + +@interface PhotoCollectionViewCell : UICollectionViewCell + +- (void)updateCellWithPhotoObject:(PhotoModel *)photo; + +@end diff --git a/examples/ASDKgram/Sample/PhotoCollectionViewCell.m b/examples/ASDKgram/Sample/PhotoCollectionViewCell.m new file mode 100644 index 0000000000..0d5d746a17 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoCollectionViewCell.m @@ -0,0 +1,57 @@ +// +// PhotoCollectionViewCell.m +// ASDKgram +// +// Created by Hannah Troisi on 3/2/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "PhotoCollectionViewCell.h" +#import "PINImageView+PINRemoteImage.h" +#import "PINButton+PINRemoteImage.h" + +@implementation PhotoCollectionViewCell +{ + UIImageView *_photoImageView; +} + +#pragma mark - Lifecycle + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) { + + _photoImageView = [[UIImageView alloc] init]; + [_photoImageView setPin_updateWithProgress:YES]; + [self.contentView addSubview:_photoImageView]; + } + + return self; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + _photoImageView.frame = self.bounds; +} + +- (void)prepareForReuse +{ + [super prepareForReuse]; + + // remove images so that the old content doesn't appear before the new content is loaded + _photoImageView.image = nil; +} + +#pragma mark - Instance Methods + +- (void)updateCellWithPhotoObject:(PhotoModel *)photo +{ + // async download of photo using PINRemoteImage + [_photoImageView pin_setImageFromURL:photo.URL]; +} + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedModel.h b/examples/ASDKgram/Sample/PhotoFeedModel.h new file mode 100644 index 0000000000..f2bb4bbbc2 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedModel.h @@ -0,0 +1,34 @@ +// +// PhotoFeedModel.h +// ASDKgram +// +// Created by Hannah Troisi on 2/28/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "PhotoModel.h" + +typedef NS_ENUM(NSInteger, PhotoFeedModelType) { + PhotoFeedModelTypePopular, + PhotoFeedModelTypeLocation, + PhotoFeedModelTypeUserPhotos +}; + +@interface PhotoFeedModel : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithPhotoFeedModelType:(PhotoFeedModelType)type imageSize:(CGSize)size NS_DESIGNATED_INITIALIZER; + +- (NSUInteger)totalNumberOfPhotos; +- (NSUInteger)numberOfItemsInFeed; +- (PhotoModel *)objectAtIndex:(NSUInteger)index; +- (NSInteger)indexOfPhotoModel:(PhotoModel *)photoModel; + +- (void)updatePhotoFeedModelTypeLocationCoordinates:(CLLocationCoordinate2D)coordinate radiusInMiles:(NSUInteger)radius; +- (void)updatePhotoFeedModelTypeUserId:(NSUInteger)userID; + +- (void)clearFeed; +- (void)requestPageWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults; +- (void)refreshFeedWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults; + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedModel.m b/examples/ASDKgram/Sample/PhotoFeedModel.m new file mode 100644 index 0000000000..fb98b15316 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedModel.m @@ -0,0 +1,238 @@ +// +// PhotoFeedModel.m +// ASDKgram +// +// Created by Hannah Troisi on 2/28/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "PhotoFeedModel.h" +#import "ImageURLModel.h" + +#define fiveHundredPX_ENDPOINT_HOST @"https://api.500px.com/v1/" +#define fiveHundredPX_ENDPOINT_POPULAR @"photos?feature=popular&exclude=Nude,People,Fashion&sort=rating&image_size=3&include_store=store_download&include_states=voted" +#define fiveHundredPX_ENDPOINT_SEARCH @"photos/search?geo=" //latitude,longitude,radius +#define fiveHundredPX_ENDPOINT_USER @"photos?user_id=" +#define fiveHundredPX_CONSUMER_KEY_PARAM @"&consumer_key=Fi13GVb8g53sGvHICzlram7QkKOlSDmAmp9s9aqC" // PLEASE REQUEST YOUR OWN 500PX CONSUMER KEY + +@implementation PhotoFeedModel +{ + PhotoFeedModelType _feedType; + + NSMutableArray *_photos; // array of PhotoModel objects + NSMutableArray *_ids; + + CGSize _imageSize; + NSString *_urlString; + NSUInteger _currentPage; + NSUInteger _totalPages; + NSUInteger _totalItems; + BOOL _fetchPageInProgress; + BOOL _refreshFeedInProgress; + NSURLSessionDataTask *_task; + + CLLocationCoordinate2D _location; + NSUInteger _locationRadius; + NSUInteger _userID; +} + +#pragma mark - Properties + +- (NSMutableArray *)photos +{ + return _photos; +} + +#pragma mark - Lifecycle + +- (instancetype)initWithPhotoFeedModelType:(PhotoFeedModelType)type imageSize:(CGSize)size +{ + self = [super init]; + + if (self) { + _feedType = type; + _imageSize = size; + _photos = [[NSMutableArray alloc] init]; + _ids = [[NSMutableArray alloc] init]; + _currentPage = 0; + + NSString *apiEndpointString; + switch (type) { + case (PhotoFeedModelTypePopular): + apiEndpointString = fiveHundredPX_ENDPOINT_POPULAR; + break; + + case (PhotoFeedModelTypeLocation): + apiEndpointString = fiveHundredPX_ENDPOINT_SEARCH; + break; + + case (PhotoFeedModelTypeUserPhotos): + apiEndpointString = fiveHundredPX_ENDPOINT_USER; + break; + + default: + break; + } + _urlString = [[fiveHundredPX_ENDPOINT_HOST stringByAppendingString:apiEndpointString] stringByAppendingString:fiveHundredPX_CONSUMER_KEY_PARAM]; + } + + return self; +} + +#pragma mark - Instance Methods + +- (NSUInteger)totalNumberOfPhotos +{ + return _totalItems; +} + +- (NSUInteger)numberOfItemsInFeed +{ + return [_photos count]; +} + +- (PhotoModel *)objectAtIndex:(NSUInteger)index +{ + return [_photos objectAtIndex:index]; +} + +- (NSInteger)indexOfPhotoModel:(PhotoModel *)photoModel +{ + return [_photos indexOfObjectIdenticalTo:photoModel]; +} + +- (void)updatePhotoFeedModelTypeLocationCoordinates:(CLLocationCoordinate2D)coordinate radiusInMiles:(NSUInteger)radius; +{ + _location = coordinate; + _locationRadius = radius; + NSString *locationString = [NSString stringWithFormat:@"%f,%f,%lumi", coordinate.latitude, coordinate.longitude, (unsigned long)radius]; + + _urlString = [fiveHundredPX_ENDPOINT_HOST stringByAppendingString:fiveHundredPX_ENDPOINT_SEARCH]; + _urlString = [[_urlString stringByAppendingString:locationString] stringByAppendingString:fiveHundredPX_CONSUMER_KEY_PARAM]; +} + +- (void)updatePhotoFeedModelTypeUserId:(NSUInteger)userID +{ + _userID = userID; + + NSString *userString = [NSString stringWithFormat:@"%lu", (long)userID]; + _urlString = [fiveHundredPX_ENDPOINT_HOST stringByAppendingString:fiveHundredPX_ENDPOINT_USER]; + _urlString = [[_urlString stringByAppendingString:userString] stringByAppendingString:@"&sort=created_at&image_size=3&include_store=store_download&include_states=voted"]; + _urlString = [_urlString stringByAppendingString:fiveHundredPX_CONSUMER_KEY_PARAM]; +} + +- (void)clearFeed +{ + _photos = [[NSMutableArray alloc] init]; + _ids = [[NSMutableArray alloc] init]; + _currentPage = 0; + _fetchPageInProgress = NO; + _refreshFeedInProgress = NO; + [_task cancel]; + _task = nil; +} + +- (void)requestPageWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults +{ + // only one fetch at a time + if (_fetchPageInProgress) { + return; + } else { + _fetchPageInProgress = YES; + [self fetchPageWithCompletionBlock:block numResultsToReturn:numResults]; + } +} + +- (void)refreshFeedWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults +{ + // only one fetch at a time + if (_refreshFeedInProgress) { + return; + + } else { + _refreshFeedInProgress = YES; + _currentPage = 0; + + // FIXME: blow away any other requests in progress + [self fetchPageWithCompletionBlock:^(NSArray *newPhotos) { + if (block) { + block(newPhotos); + } + _refreshFeedInProgress = NO; + } numResultsToReturn:numResults replaceData:YES]; + } +} + +#pragma mark - Helper Methods + +- (void)fetchPageWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults +{ + [self fetchPageWithCompletionBlock:block numResultsToReturn:numResults replaceData:NO]; +} + +- (void)fetchPageWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults replaceData:(BOOL)replaceData +{ + // early return if reached end of pages + if (_totalPages) { + if (_currentPage == _totalPages) { + return; + } + } + + NSUInteger numPhotos = (numResults < 100) ? numResults : 100; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSMutableArray *newPhotos = [NSMutableArray array]; + NSMutableArray *newIDs = [NSMutableArray array]; + + @synchronized(self) { + NSUInteger nextPage = _currentPage + 1; + NSString *imageSizeParam = [ImageURLModel imageParameterForClosestImageSize:_imageSize]; + NSString *urlAdditions = [NSString stringWithFormat:@"&page=%lu&rpp=%lu%@", (unsigned long)nextPage, (long)numPhotos, imageSizeParam]; + NSURL *url = [NSURL URLWithString:[_urlString stringByAppendingString:urlAdditions]]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; + _task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if (data) { + NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; + + if ([response isKindOfClass:[NSDictionary class]]) { + _currentPage = [[response valueForKeyPath:@"current_page"] integerValue]; + _totalPages = [[response valueForKeyPath:@"total_pages"] integerValue]; + _totalItems = [[response valueForKeyPath:@"total_items"] integerValue]; + + NSArray *photos = [response valueForKeyPath:@"photos"]; + if ([photos isKindOfClass:[NSArray class]]) { + for (NSDictionary *photoDictionary in photos) { + if ([response isKindOfClass:[NSDictionary class]]) { + PhotoModel *photo = [[PhotoModel alloc] initWith500pxPhoto:photoDictionary]; + if (photo) { + if (replaceData || ![_ids containsObject:photo.photoID]) { + [newPhotos addObject:photo]; + [newIDs addObject:photo.photoID]; + } + } + } + } + } + } + } + dispatch_async(dispatch_get_main_queue(), ^{ + if (replaceData) { + _photos = [newPhotos mutableCopy]; + _ids = [newIDs mutableCopy]; + } else { + [_photos addObjectsFromArray:newPhotos]; + [_ids addObjectsFromArray:newIDs]; + } + if (block) { + block(newPhotos); + } + _fetchPageInProgress = NO; + }); + }]; + [_task resume]; + } + }); +} + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedNodeController.h b/examples/ASDKgram/Sample/PhotoFeedNodeController.h new file mode 100644 index 0000000000..a0e72c1ae4 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedNodeController.h @@ -0,0 +1,14 @@ +// +// PhotoFeedNodeController.h +// ASDKgram +// +// Created by Hannah Troisi on 2/17/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import +#import "AppDelegate.h" + +@interface PhotoFeedNodeController : ASViewController + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedNodeController.m b/examples/ASDKgram/Sample/PhotoFeedNodeController.m new file mode 100644 index 0000000000..4eb9247bc9 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedNodeController.m @@ -0,0 +1,184 @@ +// +// PhotoFeedNodeController.m +// ASDKgram +// +// Created by Hannah Troisi on 2/17/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "PhotoFeedNodeController.h" +#import +#import "Utilities.h" +#import "PhotoModel.h" +#import "PhotoCellNode.h" +#import "PhotoFeedModel.h" + +#define AUTO_TAIL_LOADING_NUM_SCREENFULS 2.5 + +@interface PhotoFeedNodeController () +@end + +@implementation PhotoFeedNodeController +{ + PhotoFeedModel *_photoFeed; + ASTableNode *_tableNode; + UIActivityIndicatorView *_activityIndicatorView; +} + +#pragma mark - Lifecycle + +- (instancetype)init +{ + _tableNode = [[ASTableNode alloc] init]; + self = [super initWithNode:_tableNode]; + + if (self) { + self.navigationItem.title = @"ASDK"; + [self.navigationController setNavigationBarHidden:YES]; + + _tableNode.dataSource = self; + _tableNode.delegate = self; + + _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + } + + return self; +} + +// do any ASDK view stuff in loadView +- (void)loadView +{ + [super loadView]; + + _photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:[self imageSizeForScreenWidth]]; + [self refreshFeed]; + + CGSize boundSize = self.view.bounds.size; + + [_activityIndicatorView sizeToFit]; + CGRect refreshRect = _activityIndicatorView.frame; + refreshRect.origin = CGPointMake((boundSize.width - _activityIndicatorView.frame.size.width) / 2.0, + (boundSize.height - _activityIndicatorView.frame.size.height) / 2.0); + _activityIndicatorView.frame = refreshRect; + + [self.view addSubview:_activityIndicatorView]; + + self.view.backgroundColor = [UIColor whiteColor]; + _tableNode.view.allowsSelection = NO; + _tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; + _tableNode.view.leadingScreensForBatching = AUTO_TAIL_LOADING_NUM_SCREENFULS; // overriding default of 2.0 +} + +#pragma mark - helper methods + +- (void)refreshFeed +{ + [_activityIndicatorView startAnimating]; + // small first batch + [_photoFeed refreshFeedWithCompletionBlock:^(NSArray *newPhotos){ + + [_activityIndicatorView stopAnimating]; + + [self insertNewRowsInTableView:newPhotos]; +// [self requestCommentsForPhotos:newPhotos]; + + // immediately start second larger fetch + [self loadPageWithContext:nil]; + + } numResultsToReturn:4]; +} + +- (void)loadPageWithContext:(ASBatchContext *)context +{ + [_photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){ + + [self insertNewRowsInTableView:newPhotos]; +// [self requestCommentsForPhotos:newPhotos]; + if (context) { + [context completeBatchFetching:YES]; + } + } numResultsToReturn:20]; +} + +//- (void)requestCommentsForPhotos:(NSArray *)newPhotos +//{ +// for (PhotoModel *photo in newPhotos) { +// [photo.commentFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) { +// +// NSInteger rowNum = [_photoFeed indexOfPhotoModel:photo]; +// NSIndexPath *cellPath = [NSIndexPath indexPathForRow:rowNum inSection:0]; +// PhotoCellNode *cell = (PhotoCellNode *)[_tableNode.view nodeForRowAtIndexPath:cellPath]; +// +// if (cell) { +// [cell loadCommentsForPhoto:photo]; +// [_tableNode.view beginUpdates]; +// [_tableNode.view endUpdates]; +// } +// }]; +// } +//} + +- (void)insertNewRowsInTableView:(NSArray *)newPhotos +{ + NSInteger section = 0; + NSMutableArray *indexPaths = [NSMutableArray array]; + + NSUInteger newTotalNumberOfPhotos = [_photoFeed numberOfItemsInFeed]; + for (NSUInteger row = newTotalNumberOfPhotos - newPhotos.count; row < newTotalNumberOfPhotos; row++) { + NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section]; + [indexPaths addObject:path]; + } + + [_tableNode.view insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle +{ + return UIStatusBarStyleLightContent; +} + +- (CGSize)imageSizeForScreenWidth +{ + CGRect screenRect = [[UIScreen mainScreen] bounds]; + CGFloat screenScale = [[UIScreen mainScreen] scale]; + return CGSizeMake(screenRect.size.width * screenScale, screenRect.size.width * screenScale); +} + +#pragma mark - ASTableDataSource methods + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return [_photoFeed numberOfItemsInFeed]; +} + +- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath +{ + PhotoModel *photoModel = [_photoFeed objectAtIndex:indexPath.row]; + // this will be executed on a background thread - important to make sure it's thread safe + ASCellNode *(^ASCellNodeBlock)() = ^ASCellNode *() { + PhotoCellNode *cellNode = [[PhotoCellNode alloc] initWithPhotoObject:photoModel]; + return cellNode; + }; + + return ASCellNodeBlock; +} + +#pragma mark - ASTableDelegate methods + +// Receive a message that the tableView is near the end of its data set and more data should be fetched if necessary. +- (void)tableView:(ASTableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context +{ + [context beginBatchFetching]; + [self loadPageWithContext:context]; +} + +#pragma mark - PhotoFeedViewControllerProtocol + +- (void)resetAllData +{ + [_photoFeed clearFeed]; + [_tableNode.view reloadData]; + [self refreshFeed]; +} + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedViewController.h b/examples/ASDKgram/Sample/PhotoFeedViewController.h new file mode 100644 index 0000000000..053812ed63 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedViewController.h @@ -0,0 +1,13 @@ +// +// PhotoFeedViewController.h +// ASDKgram +// +// Created by Hannah Troisi on 2/17/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "AppDelegate.h" + +@interface PhotoFeedViewController : UIViewController + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedViewController.m b/examples/ASDKgram/Sample/PhotoFeedViewController.m new file mode 100644 index 0000000000..1336ea5fe1 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedViewController.m @@ -0,0 +1,197 @@ +// +// PhotoFeedViewController.m +// ASDKgram +// +// Created by Hannah Troisi on 2/17/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "PhotoFeedViewController.h" +#import "Utilities.h" +#import "PhotoTableViewCell.h" +#import "PhotoFeedModel.h" +#import "CommentView.h" + +#define AUTO_TAIL_LOADING_NUM_SCREENFULS 2.5 + +@interface PhotoFeedViewController () +@end + +@implementation PhotoFeedViewController +{ + PhotoFeedModel *_photoFeed; + UITableView *_tableView; + UIActivityIndicatorView *_activityIndicatorView; +} + +#pragma mark - Lifecycle + +- (instancetype)init +{ + self = [super initWithNibName:nil bundle:nil]; + + if (self) { + self.navigationItem.title = @"UIKit"; + [self.navigationController setNavigationBarHidden:YES]; + + _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; + _tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _tableView.delegate = self; + _tableView.dataSource = self; + + _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + } + + return self; +} + +// anything involving the view should go here, not init +- (void)viewDidLoad +{ + [super viewDidLoad]; + + _photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:[self imageSizeForScreenWidth]]; + [self refreshFeed]; + + CGSize boundSize = self.view.bounds.size; + + [self.view addSubview:_tableView]; + + _tableView.frame = self.view.bounds; + _tableView.allowsSelection = NO; + _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + [_tableView registerClass:[PhotoTableViewCell class] forCellReuseIdentifier:@"photoCell"]; + + [self.view addSubview:_activityIndicatorView]; + + [_activityIndicatorView sizeToFit]; + CGRect refreshRect = _activityIndicatorView.frame; + refreshRect.origin = CGPointMake((boundSize.width - _activityIndicatorView.frame.size.width) / 2.0, + (boundSize.height - _activityIndicatorView.frame.size.height) / 2.0); + _activityIndicatorView.frame = refreshRect; +} + +#pragma mark - helper methods + +- (void)refreshFeed +{ + [_activityIndicatorView startAnimating]; + + // small first batch + [_photoFeed refreshFeedWithCompletionBlock:^(NSArray *newPhotos){ + + [_activityIndicatorView stopAnimating]; + + [self insertNewRowsInTableView:newPhotos]; + [self requestCommentsForPhotos:newPhotos]; + + // immediately start second larger fetch + [self loadPage]; + + } numResultsToReturn:4]; +} + +- (void)loadPage +{ + [_photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){ + [self insertNewRowsInTableView:newPhotos]; + [self requestCommentsForPhotos:newPhotos]; + } numResultsToReturn:20]; +} + +- (void)requestCommentsForPhotos:(NSArray *)newPhotos +{ + for (PhotoModel *photo in newPhotos) { + [photo.commentFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) { + + NSInteger rowNum = [_photoFeed indexOfPhotoModel:photo]; + NSIndexPath *cellPath = [NSIndexPath indexPathForRow:rowNum inSection:0]; + PhotoTableViewCell *cell = [_tableView cellForRowAtIndexPath:cellPath]; + + if (cell) { + [cell loadCommentsForPhoto:photo]; + [_tableView beginUpdates]; + [_tableView endUpdates]; + + // adjust scrollView contentOffset if inserting above visible cells + NSIndexPath *visibleCellPath = [_tableView indexPathForCell:_tableView.visibleCells.firstObject]; + if (cellPath.row < visibleCellPath.row) { + CGFloat commentViewHeight = [CommentView heightForCommentFeedModel:photo.commentFeed withWidth:self.view.bounds.size.width]; + _tableView.contentOffset = CGPointMake(_tableView.contentOffset.x, _tableView.contentOffset.y + commentViewHeight); + } + } + }]; + } +} + +- (void)insertNewRowsInTableView:(NSArray *)newPhotos +{ + NSInteger section = 0; + NSMutableArray *indexPaths = [NSMutableArray array]; + + NSInteger newTotalNumberOfPhotos = [_photoFeed numberOfItemsInFeed]; + for (NSInteger row = newTotalNumberOfPhotos - newPhotos.count; row < newTotalNumberOfPhotos; row++) { + NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section]; + [indexPaths addObject:path]; + } + [_tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle +{ + return UIStatusBarStyleLightContent; +} + +- (CGSize)imageSizeForScreenWidth +{ + CGRect screenRect = [[UIScreen mainScreen] bounds]; + CGFloat screenScale = [[UIScreen mainScreen] scale]; + return CGSizeMake(screenRect.size.width * screenScale, screenRect.size.width * screenScale); +} + +#pragma mark - UITableViewDataSource methods + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return [_photoFeed numberOfItemsInFeed]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + PhotoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"photoCell" forIndexPath:indexPath]; + [cell updateCellWithPhotoObject:[_photoFeed objectAtIndex:indexPath.row]]; + + return cell; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath +{ + PhotoModel *photo = [_photoFeed objectAtIndex:indexPath.row]; + return [PhotoTableViewCell heightForPhotoModel:photo withWidth:self.view.bounds.size.width]; +} + +#pragma mark - UITableViewDelegate methods + +// table automatic tail loading +-(void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + CGFloat currentOffSetY = scrollView.contentOffset.y; + CGFloat contentHeight = scrollView.contentSize.height; + CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height; + + CGFloat screenfullsBeforeBottom = (contentHeight - currentOffSetY) / screenHeight; + if (screenfullsBeforeBottom < AUTO_TAIL_LOADING_NUM_SCREENFULS) { + [self loadPage]; + } +} + +#pragma mark - PhotoFeedViewControllerProtocol + +- (void)resetAllData +{ + [_photoFeed clearFeed]; + [_tableView reloadData]; + [self refreshFeed]; +} + +@end diff --git a/examples/ASDKgram/Sample/PhotoModel.h b/examples/ASDKgram/Sample/PhotoModel.h new file mode 100644 index 0000000000..d89c688fca --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoModel.h @@ -0,0 +1,35 @@ +// +// PhotoModel.h +// ASDKgram +// +// Created by Hannah Troisi on 2/26/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "CoreGraphics/CoreGraphics.h" +#import "UserModel.h" +#import "LocationModel.h" +#import "CommentFeedModel.h" + +@interface PhotoModel : NSObject + +@property (nonatomic, strong, readonly) NSURL *URL; +@property (nonatomic, strong, readonly) NSString *photoID; +@property (nonatomic, strong, readonly) NSString *uploadDateString; +@property (nonatomic, strong, readonly) NSString *title; +@property (nonatomic, strong, readonly) NSString *descriptionText; +@property (nonatomic, assign, readonly) NSUInteger commentsCount; +@property (nonatomic, assign, readonly) NSUInteger likesCount; +@property (nonatomic, strong, readonly) LocationModel *location; +@property (nonatomic, strong, readonly) UserModel *ownerUserProfile; +@property (nonatomic, strong, readonly) CommentFeedModel *commentFeed; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWith500pxPhoto:(NSDictionary *)photoDictionary NS_DESIGNATED_INITIALIZER; + +- (NSAttributedString *)descriptionAttributedStringWithFontSize:(CGFloat)size; +- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size; +- (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size; +- (NSAttributedString *)locationAttributedStringWithFontSize:(CGFloat)size; + +@end diff --git a/examples/ASDKgram/Sample/PhotoModel.m b/examples/ASDKgram/Sample/PhotoModel.m new file mode 100644 index 0000000000..b05005c5e0 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoModel.m @@ -0,0 +1,94 @@ +// +// PhotoModel.m +// ASDKgram +// +// Created by Hannah Troisi on 2/26/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "PhotoModel.h" +#import "Utilities.h" + +@implementation PhotoModel +{ + NSDictionary *_dictionaryRepresentation; + NSString *_uploadDateRaw; + CommentFeedModel *_commentFeed; +} + +#pragma mark - Properties + +- (CommentFeedModel *)commentFeed +{ + if (!_commentFeed) { + _commentFeed = [[CommentFeedModel alloc] initWithPhotoID:_photoID]; + } + + return _commentFeed; +} + +#pragma mark - Lifecycle + +- (instancetype)initWith500pxPhoto:(NSDictionary *)photoDictionary +{ + self = [super init]; + + if (self) { + _dictionaryRepresentation = photoDictionary; + _uploadDateRaw = [photoDictionary objectForKey:@"created_at"]; + _photoID = [[photoDictionary objectForKey:@"id"] description]; + _title = [photoDictionary objectForKey:@"title"]; + _descriptionText = [photoDictionary valueForKeyPath:@"name"]; + _commentsCount = [[photoDictionary objectForKey:@"comments_count"] integerValue]; + _likesCount = [[photoDictionary objectForKey:@"positive_votes_count"] integerValue]; + + NSString *urlString = [photoDictionary objectForKey:@"image_url"]; + _URL = urlString ? [NSURL URLWithString:urlString] : nil; + + _location = [[LocationModel alloc] initWith500pxPhoto:photoDictionary]; + _ownerUserProfile = [[UserModel alloc] initWith500pxPhoto:photoDictionary]; + _uploadDateString = [NSString elapsedTimeStringSinceDate:_uploadDateRaw]; + } + + return self; +} + +#pragma mark - Instance Methods + +- (NSAttributedString *)descriptionAttributedStringWithFontSize:(CGFloat)size +{ + NSString *string = [NSString stringWithFormat:@"%@ %@", self.ownerUserProfile.username, self.descriptionText]; + NSAttributedString *attrString = [NSAttributedString attributedStringWithString:string + fontSize:size + color:[UIColor darkGrayColor] + firstWordColor:[UIColor darkBlueColor]]; + return attrString; +} + +- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:self.uploadDateString fontSize:size color:[UIColor lightGrayColor] firstWordColor:nil]; +} + +- (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size +{ + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; + NSString *formattedLikesNumber = [formatter stringFromNumber:[[NSNumber alloc] initWithUnsignedInteger:self.likesCount]]; + + NSString *likesString = [NSString stringWithFormat:@"♥︎ %@ likes", formattedLikesNumber]; + + return [NSAttributedString attributedStringWithString:likesString fontSize:size color:[UIColor darkBlueColor] firstWordColor:nil]; +} + +- (NSAttributedString *)locationAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:self.location.locationString fontSize:size color:[UIColor lightBlueColor] firstWordColor:nil]; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ - %@", _photoID, _descriptionText]; +} + +@end \ No newline at end of file diff --git a/examples/ASDKgram/Sample/PhotoTableViewCell.h b/examples/ASDKgram/Sample/PhotoTableViewCell.h new file mode 100644 index 0000000000..c464f99630 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoTableViewCell.h @@ -0,0 +1,19 @@ +// +// PhotoTableViewCell.h +// ASDKgram +// +// Created by Hannah Troisi on 2/17/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import +#import "PhotoModel.h" + +@interface PhotoTableViewCell : UITableViewCell + ++ (CGFloat)heightForPhotoModel:(PhotoModel *)photo withWidth:(CGFloat)width; + +- (void)updateCellWithPhotoObject:(PhotoModel *)photo; +- (void)loadCommentsForPhoto:(PhotoModel *)photo; + +@end diff --git a/examples/ASDKgram/Sample/PhotoTableViewCell.m b/examples/ASDKgram/Sample/PhotoTableViewCell.m new file mode 100644 index 0000000000..a96883dffb --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoTableViewCell.m @@ -0,0 +1,493 @@ +// +// PhotoTableViewCell.m +// ASDKgram +// +// Created by Hannah Troisi on 2/17/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "PhotoTableViewCell.h" +#import "Utilities.h" +#import "PINImageView+PINRemoteImage.h" +#import "PINButton+PINRemoteImage.h" +#import "CommentView.h" + +#define DEBUG_PHOTOCELL_LAYOUT 0 +#define USE_UIKIT_AUTOLAYOUT 1 +#define USE_UIKIT_MANUAL_LAYOUT !USE_UIKIT_AUTOLAYOUT + +#define HEADER_HEIGHT 50 +#define USER_IMAGE_HEIGHT 30 +#define HORIZONTAL_BUFFER 10 +#define VERTICAL_BUFFER 5 +#define FONT_SIZE 14 + +@implementation PhotoTableViewCell +{ + PhotoModel *_photoModel; + CommentView *_photoCommentsView; + + UIImageView *_userAvatarImageView; + UIImageView *_photoImageView; + UILabel *_userNameLabel; + UILabel *_photoLocationLabel; + UILabel *_photoTimeIntervalSincePostLabel; + UILabel *_photoLikesLabel; + UILabel *_photoDescriptionLabel; + + NSLayoutConstraint *_userNameYPositionWithPhotoLocation; + NSLayoutConstraint *_userNameYPositionWithoutPhotoLocation; + NSLayoutConstraint *_photoLocationYPosition; +} + +#pragma mark - Class Methods + ++ (CGFloat)heightForPhotoModel:(PhotoModel *)photo withWidth:(CGFloat)width; +{ + CGFloat photoHeight = width; + + UIFont *font = [UIFont systemFontOfSize:FONT_SIZE]; + CGFloat likesHeight = roundf([font lineHeight]); + + NSAttributedString *descriptionAttrString = [photo descriptionAttributedStringWithFontSize:FONT_SIZE]; + CGFloat availableWidth = (width - HORIZONTAL_BUFFER * 2); + CGFloat descriptionHeight = [descriptionAttrString boundingRectWithSize:CGSizeMake(availableWidth, CGFLOAT_MAX) + options:NSStringDrawingUsesLineFragmentOrigin + context:nil].size.height; + + CGFloat commentViewHeight = [CommentView heightForCommentFeedModel:photo.commentFeed withWidth:availableWidth]; + + return HEADER_HEIGHT + photoHeight + likesHeight + descriptionHeight + commentViewHeight + (4 * VERTICAL_BUFFER); +} + +#pragma mark - Lifecycle + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier +{ + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + + if (self) { + + _photoCommentsView = [[CommentView alloc] init]; + _userAvatarImageView = [[UIImageView alloc] init]; + _photoImageView = [[UIImageView alloc] init]; + _userNameLabel = [[UILabel alloc] init]; + _photoLocationLabel = [[UILabel alloc] init]; + _photoTimeIntervalSincePostLabel = [[UILabel alloc] init]; + _photoLikesLabel = [[UILabel alloc] init]; + _photoDescriptionLabel = [[UILabel alloc] init]; + _photoDescriptionLabel.numberOfLines = 3; + + [self addSubview:_photoCommentsView]; + [self addSubview:_userAvatarImageView]; + [self addSubview:_photoImageView]; + [self addSubview:_userNameLabel]; + [self addSubview:_photoLocationLabel]; + [self addSubview:_photoTimeIntervalSincePostLabel]; + [self addSubview:_photoLikesLabel]; + [self addSubview:_photoDescriptionLabel]; + +#if USE_UIKIT_AUTOLAYOUT + [_photoCommentsView setTranslatesAutoresizingMaskIntoConstraints:NO]; + [_userAvatarImageView setTranslatesAutoresizingMaskIntoConstraints:NO]; + [_photoImageView setTranslatesAutoresizingMaskIntoConstraints:NO]; + [_userNameLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; + [_photoLocationLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; + [_photoTimeIntervalSincePostLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; + [_photoLikesLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; + [_photoDescriptionLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; + [_photoCommentsView setTranslatesAutoresizingMaskIntoConstraints:NO]; + + [self setupConstraints]; + [self updateConstraints]; +#endif + +#if DEBUG_PHOTOCELL_LAYOUT + _userAvatarImageView.backgroundColor = [UIColor greenColor]; + _userNameLabel.backgroundColor = [UIColor greenColor]; + _photoLocationLabel.backgroundColor = [UIColor greenColor]; + _photoTimeIntervalSincePostLabel.backgroundColor = [UIColor greenColor]; + _photoCommentsView.backgroundColor = [UIColor greenColor]; + _photoDescriptionLabel.backgroundColor = [UIColor greenColor]; + _photoLikesLabel.backgroundColor = [UIColor greenColor]; +#endif + } + + return self; +} + +-(void)setFrame:(CGRect)frame +{ + [super setFrame:frame]; +} + +- (void)setupConstraints +{ + // _userAvatarImageView + [self addConstraint:[NSLayoutConstraint constraintWithItem:_userAvatarImageView + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:_userAvatarImageView.superview + attribute:NSLayoutAttributeLeft + multiplier:1.0 + constant:HORIZONTAL_BUFFER]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_userAvatarImageView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:_userAvatarImageView.superview + attribute:NSLayoutAttributeTop + multiplier:1.0 + constant:HORIZONTAL_BUFFER]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_userAvatarImageView + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:0.0 + constant:USER_IMAGE_HEIGHT]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_userAvatarImageView + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:_userAvatarImageView + attribute:NSLayoutAttributeWidth + multiplier:1.0 + constant:0.0]]; + + // _userNameLabel + [self addConstraint:[NSLayoutConstraint constraintWithItem:_userNameLabel + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:_userAvatarImageView + attribute:NSLayoutAttributeRight + multiplier:1.0 + constant:HORIZONTAL_BUFFER]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_userNameLabel + attribute:NSLayoutAttributeRight + relatedBy:NSLayoutRelationLessThanOrEqual + toItem:_photoTimeIntervalSincePostLabel + attribute:NSLayoutAttributeLeft + multiplier:1.0 + constant:-HORIZONTAL_BUFFER]]; + + _userNameYPositionWithoutPhotoLocation = [NSLayoutConstraint constraintWithItem:_userNameLabel + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:_userAvatarImageView + attribute:NSLayoutAttributeCenterY + multiplier:1.0 + constant:0.0]; + [self addConstraint:_userNameYPositionWithoutPhotoLocation]; + + _userNameYPositionWithPhotoLocation = [NSLayoutConstraint constraintWithItem:_userNameLabel + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:_userAvatarImageView + attribute:NSLayoutAttributeTop + multiplier:1.0 + constant:-2]; + _userNameYPositionWithPhotoLocation.active = NO; + [self addConstraint:_userNameYPositionWithPhotoLocation]; + + // _photoLocationLabel + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoLocationLabel + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:_userAvatarImageView + attribute:NSLayoutAttributeRight + multiplier:1.0 + constant:HORIZONTAL_BUFFER]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoLocationLabel + attribute:NSLayoutAttributeRight + relatedBy:NSLayoutRelationLessThanOrEqual + toItem:_photoTimeIntervalSincePostLabel + attribute:NSLayoutAttributeLeft + multiplier:1.0 + constant:-HORIZONTAL_BUFFER]]; + + _photoLocationYPosition = [NSLayoutConstraint constraintWithItem:_photoLocationLabel + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:_userAvatarImageView + attribute:NSLayoutAttributeBottom + multiplier:1.0 + constant:2]; + _photoLocationYPosition.active = NO; + [self addConstraint:_photoLocationYPosition]; + + // _photoTimeIntervalSincePostLabel + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoTimeIntervalSincePostLabel + attribute:NSLayoutAttributeRight + relatedBy:NSLayoutRelationEqual + toItem:_photoTimeIntervalSincePostLabel.superview + attribute:NSLayoutAttributeRight + multiplier:1.0 + constant:-HORIZONTAL_BUFFER]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoTimeIntervalSincePostLabel + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:_userAvatarImageView + attribute:NSLayoutAttributeCenterY + multiplier:1.0 + constant:0.0]]; + + // _photoImageView + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoImageView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:_photoImageView.superview + attribute:NSLayoutAttributeTop + multiplier:1.0 + constant:HEADER_HEIGHT]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoImageView + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeWidth + multiplier:1.0 + constant:0.0]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoImageView + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:_photoImageView + attribute:NSLayoutAttributeWidth + multiplier:1.0 + constant:0.0]]; + + // _photoLikesLabel + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoLikesLabel + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:_photoImageView + attribute:NSLayoutAttributeBottom + multiplier:1.0 + constant:VERTICAL_BUFFER]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoLikesLabel + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:_photoLikesLabel.superview + attribute:NSLayoutAttributeLeft + multiplier:1.0 + constant:HORIZONTAL_BUFFER]]; + + // _photoDescriptionLabel + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoDescriptionLabel + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:_photoLikesLabel + attribute:NSLayoutAttributeBottom + multiplier:1.0 + constant:VERTICAL_BUFFER]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoDescriptionLabel + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:_photoDescriptionLabel.superview + attribute:NSLayoutAttributeLeft + multiplier:1.0 + constant:HORIZONTAL_BUFFER]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoDescriptionLabel + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:_photoDescriptionLabel.superview + attribute:NSLayoutAttributeWidth + multiplier:1.0 + constant:-HORIZONTAL_BUFFER]]; + + // _photoCommentsView + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoCommentsView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:_photoDescriptionLabel + attribute:NSLayoutAttributeBottom + multiplier:1.0 + constant:VERTICAL_BUFFER]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoCommentsView + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:_photoCommentsView.superview + attribute:NSLayoutAttributeLeft + multiplier:1.0 + constant:HORIZONTAL_BUFFER]]; + + [self addConstraint:[NSLayoutConstraint constraintWithItem:_photoCommentsView + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:_photoCommentsView.superview + attribute:NSLayoutAttributeWidth + multiplier:1.0 + constant:-HORIZONTAL_BUFFER]]; +} + +- (void)updateConstraints +{ + [super updateConstraints]; + + if (_photoLocationLabel.attributedText) { + _userNameYPositionWithoutPhotoLocation.active = NO; + _userNameYPositionWithPhotoLocation.active = YES; + _photoLocationYPosition.active = YES; + } else { + _userNameYPositionWithoutPhotoLocation.active = YES; + _userNameYPositionWithPhotoLocation.active = NO; + _photoLocationYPosition.active = NO; + } +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + +#if USE_UIKIT_PROGRAMMATIC_LAYOUT + CGSize boundsSize = self.bounds.size; + + CGRect rect = CGRectMake(HORIZONTAL_BUFFER, (HEADER_HEIGHT - USER_IMAGE_HEIGHT) / 2.0, + USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); + _userAvatarImageView.frame = rect; + + rect.size = _photoTimeIntervalSincePostLabel.bounds.size; + rect.origin.x = boundsSize.width - HORIZONTAL_BUFFER - rect.size.width; + rect.origin.y = (HEADER_HEIGHT - rect.size.height) / 2.0; + _photoTimeIntervalSincePostLabel.frame = rect; + + CGFloat availableWidth = CGRectGetMinX(_photoTimeIntervalSincePostLabel.frame) - HORIZONTAL_BUFFER; + rect.size = _userNameLabel.bounds.size; + rect.size.width = MIN(availableWidth, rect.size.width); + + rect.origin.x = HORIZONTAL_BUFFER + USER_IMAGE_HEIGHT + HORIZONTAL_BUFFER; + + if (_photoLocationLabel.attributedText) { + CGSize locationSize = _photoLocationLabel.bounds.size; + locationSize.width = MIN(availableWidth, locationSize.width); + + rect.origin.y = (HEADER_HEIGHT - rect.size.height - locationSize.height) / 2.0; + _userNameLabel.frame = rect; + + // FIXME: Name rects at least for this sub-condition + rect.origin.y += rect.size.height; + rect.size = locationSize; + _photoLocationLabel.frame = rect; + } else { + rect.origin.y = (HEADER_HEIGHT - rect.size.height) / 2.0; + _userNameLabel.frame = rect; + } + + _photoImageView.frame = CGRectMake(0, HEADER_HEIGHT, boundsSize.width, boundsSize.width); + + // FIXME: Make PhotoCellFooterView + rect.size = _photoLikesLabel.bounds.size; + rect.origin = CGPointMake(HORIZONTAL_BUFFER, CGRectGetMaxY(_photoImageView.frame) + VERTICAL_BUFFER); + _photoLikesLabel.frame = rect; + + rect.size = _photoDescriptionLabel.bounds.size; + rect.size.width = MIN(boundsSize.width - HORIZONTAL_BUFFER * 2, rect.size.width); + rect.origin.y = CGRectGetMaxY(_photoLikesLabel.frame) + VERTICAL_BUFFER; + _photoDescriptionLabel.frame = rect; + + rect.size = _photoCommentsView.bounds.size; + rect.size.width = boundsSize.width - HORIZONTAL_BUFFER * 2; + rect.origin.y = CGRectGetMaxY(_photoDescriptionLabel.frame) + VERTICAL_BUFFER; + _photoCommentsView.frame = rect; +#endif +} + +- (void)prepareForReuse +{ + [super prepareForReuse]; + + _photoCommentsView.frame = CGRectZero; // next cell might not have a _photoCommentsView + [_photoCommentsView updateWithCommentFeedModel:nil]; + + _userAvatarImageView.image = nil; + _photoImageView.image = nil; + _userNameLabel.attributedText = nil; + _photoLocationLabel.attributedText = nil; + _photoLocationLabel.frame = CGRectZero; // next cell might not have a _photoLocationLabel + _photoTimeIntervalSincePostLabel.attributedText = nil; + _photoLikesLabel.attributedText = nil; + _photoDescriptionLabel.attributedText = nil; +} + +#pragma mark - Instance Methods + +- (void)updateCellWithPhotoObject:(PhotoModel *)photo +{ + _photoModel = photo; + _userNameLabel.attributedText = [photo.ownerUserProfile usernameAttributedStringWithFontSize:FONT_SIZE]; + _photoTimeIntervalSincePostLabel.attributedText = [photo uploadDateAttributedStringWithFontSize:FONT_SIZE]; + _photoLikesLabel.attributedText = [photo likesAttributedStringWithFontSize:FONT_SIZE]; + _photoDescriptionLabel.attributedText = [photo descriptionAttributedStringWithFontSize:FONT_SIZE]; + + [_userNameLabel sizeToFit]; + [_photoTimeIntervalSincePostLabel sizeToFit]; + [_photoLikesLabel sizeToFit]; + [_photoDescriptionLabel sizeToFit]; + CGRect rect = _photoDescriptionLabel.frame; + CGFloat availableWidth = (self.bounds.size.width - HORIZONTAL_BUFFER * 2); + rect.size = [_photoDescriptionLabel sizeThatFits:CGSizeMake(availableWidth, CGFLOAT_MAX)]; + _photoDescriptionLabel.frame = rect; + + [UIImage downloadImageForURL:photo.URL completion:^(UIImage *image) { + _photoImageView.image = image; + }]; + + [self downloadAndProcessUserAvatarForPhoto:photo]; + [self loadCommentsForPhoto:photo]; + [self reverseGeocodeLocationForPhoto:photo]; +} + +- (void)loadCommentsForPhoto:(PhotoModel *)photo +{ + if (photo.commentFeed.numberOfItemsInFeed > 0) { + [_photoCommentsView updateWithCommentFeedModel:photo.commentFeed]; + + CGRect frame = _photoCommentsView.frame; + CGFloat availableWidth = (self.bounds.size.width - HORIZONTAL_BUFFER * 2); + frame.size.width = availableWidth; + frame.size.height = [CommentView heightForCommentFeedModel:photo.commentFeed withWidth:availableWidth]; + _photoCommentsView.frame = frame; + + [self setNeedsLayout]; + } +} + +#pragma mark - Helper Methods + +- (void)downloadAndProcessUserAvatarForPhoto:(PhotoModel *)photo +{ + [UIImage downloadImageForURL:photo.URL completion:^(UIImage *image) { + CGSize profileImageSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); + _userAvatarImageView.image = [image makeCircularImageWithSize:profileImageSize]; + }]; +} + +- (void)reverseGeocodeLocationForPhoto:(PhotoModel *)photo +{ + [photo.location reverseGeocodedLocationWithCompletionBlock:^(LocationModel *locationModel) { + + // check and make sure this is still relevant for this cell (and not an old cell) + // make sure to use _photoModel instance variable as photo may change when cell is reused, + // where as local variable will never change + if (locationModel == _photoModel.location) { + _photoLocationLabel.attributedText = [photo locationAttributedStringWithFontSize:FONT_SIZE]; + [_photoLocationLabel sizeToFit]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateConstraints]; + [self setNeedsLayout]; + }); + } + }]; +} + +@end diff --git a/examples/ASDKgram/Sample/Sample.pch b/examples/ASDKgram/Sample/Sample.pch new file mode 100644 index 0000000000..8c35575c9b --- /dev/null +++ b/examples/ASDKgram/Sample/Sample.pch @@ -0,0 +1,15 @@ +// +// ASDKgram.pch +// ASDKgram +// +// Created by Hannah Troisi on 2/26/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#ifndef Flickrgram_pch +#define Flickrgram_pch + +#import +#import + +#endif /* Flickrgram_pch */ diff --git a/examples/ASDKgram/Sample/UserModel.h b/examples/ASDKgram/Sample/UserModel.h new file mode 100644 index 0000000000..4190c3612e --- /dev/null +++ b/examples/ASDKgram/Sample/UserModel.h @@ -0,0 +1,40 @@ +// +// UserModel.h +// ASDKgram +// +// Created by Hannah Troisi on 2/26/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +@interface UserModel : NSObject + +@property (nonatomic, strong, readonly) NSDictionary *dictionaryRepresentation; +@property (nonatomic, assign, readonly) NSUInteger userID; +@property (nonatomic, strong, readonly) NSString *username; +@property (nonatomic, strong, readonly) NSString *firstName; +@property (nonatomic, strong, readonly) NSString *lastName; +@property (nonatomic, strong, readonly) NSString *fullName; +@property (nonatomic, strong, readonly) NSString *city; +@property (nonatomic, strong, readonly) NSString *state; +@property (nonatomic, strong, readonly) NSString *country; +@property (nonatomic, strong, readonly) NSString *about; +@property (nonatomic, strong, readonly) NSString *domain; +@property (nonatomic, strong, readonly) NSURL *userPicURL; +@property (nonatomic, assign, readonly) NSUInteger photoCount; +@property (nonatomic, assign, readonly) NSUInteger galleriesCount; +@property (nonatomic, assign, readonly) NSUInteger affection; +@property (nonatomic, assign, readonly) NSUInteger friendsCount; +@property (nonatomic, assign, readonly) NSUInteger followersCount; +@property (nonatomic, assign, readonly) BOOL following; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWith500pxPhoto:(NSDictionary *)dictionary NS_DESIGNATED_INITIALIZER; + +- (NSAttributedString *)usernameAttributedStringWithFontSize:(CGFloat)size; +- (NSAttributedString *)fullNameAttributedStringWithFontSize:(CGFloat)size; + +- (void)fetchAvatarImageWithCompletionBlock:(void(^)(UserModel *, UIImage *))block; + +- (void)downloadCompleteUserDataWithCompletionBlock:(void(^)(UserModel *))block; + +@end \ No newline at end of file diff --git a/examples/ASDKgram/Sample/UserModel.m b/examples/ASDKgram/Sample/UserModel.m new file mode 100644 index 0000000000..01c5acb7fb --- /dev/null +++ b/examples/ASDKgram/Sample/UserModel.m @@ -0,0 +1,173 @@ +// +// UserModel.m +// ASDKgram +// +// Created by Hannah Troisi on 2/26/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "UserModel.h" +#import "Utilities.h" + +@implementation UserModel +{ + BOOL _fullUserInfoFetchRequested; + BOOL _fullUserInfoFetchDone; + void (^_fullUserInfoCompletionBlock)(UserModel *); +} + +#pragma mark - Lifecycle + +- (instancetype)initWith500pxPhoto:(NSDictionary *)dictionary +{ + self = [super init]; + + if (self) { + _fullUserInfoFetchRequested = NO; + _fullUserInfoFetchDone = NO; + + [self loadUserDataFromDictionary:dictionary]; + } + + return self; +} + +#pragma mark - Instance Methods + +- (NSAttributedString *)usernameAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:self.username fontSize:size color:[UIColor darkBlueColor] firstWordColor:nil]; +} + +- (NSAttributedString *)fullNameAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:self.fullName fontSize:size color:[UIColor lightGrayColor] firstWordColor:nil]; +} + +- (void)fetchAvatarImageWithCompletionBlock:(void(^)(UserModel *, UIImage *))block +{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; + NSURLSessionDataTask *task = [session dataTaskWithURL:_userPicURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if (data) { + UIImage *image = [UIImage imageWithData:data]; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (block) { + block(self, image); + } + }); + } + }]; + [task resume]; + }); +} + +- (void)downloadCompleteUserDataWithCompletionBlock:(void(^)(UserModel *))block; +{ + if (_fullUserInfoFetchDone) { + NSAssert(!_fullUserInfoCompletionBlock, @"Should not have a waiting block at this point"); + // complete user info fetch complete - excute completion block + if (block) { + block(self); + } + + } else { + NSAssert(!_fullUserInfoCompletionBlock, @"Should not have a waiting block at this point"); + // set completion block + _fullUserInfoCompletionBlock = block; + + if (!_fullUserInfoFetchRequested) { + // if fetch not in progress, beging + [self fetchCompleteUserData]; + } + } +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@", self.dictionaryRepresentation]; +} + +#pragma mark - Helper Methods + +- (void)fetchCompleteUserData +{ + _fullUserInfoFetchRequested = YES; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + // fetch JSON data from server + NSString *urlString = [NSString stringWithFormat:@"https://api.500px.com/v1/users/show?id=%lu&consumer_key=Fi13GVb8g53sGvHICzlram7QkKOlSDmAmp9s9aqC", (unsigned long)_userID]; + + NSURL *url = [NSURL URLWithString:urlString]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; + NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if (data) { + NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + + // parse JSON data + if ([response isKindOfClass:[NSDictionary class]]) { + [self loadUserDataFromDictionary:response]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + _fullUserInfoFetchDone = YES; + + if (_fullUserInfoCompletionBlock) { + _fullUserInfoCompletionBlock(self); + + // IT IS ESSENTIAL to nil the block, as it retains a view controller BECAUSE it uses an instance variable which + // means that self is retained. It could continue to live on forever + // If we don't release this. + _fullUserInfoCompletionBlock = nil; + } + }); + } + }]; + [task resume]; + }); +} + +- (void)loadUserDataFromDictionary:(NSDictionary *)dictionary +{ + NSDictionary *userDictionary = [dictionary objectForKey:@"user"]; + if (![userDictionary isKindOfClass:[NSDictionary class]]) { + return; + } + + _userID = [[self guardJSONElement:[userDictionary objectForKey:@"id"]] integerValue]; + _username = [[self guardJSONElement:[userDictionary objectForKey:@"username"]] lowercaseString]; + + if ([_username isKindOfClass:[NSNumber class]]) { + _username = @"Anonymous"; + } + + _firstName = [self guardJSONElement:[userDictionary objectForKey:@"firstname"]]; + _lastName = [self guardJSONElement:[userDictionary objectForKey:@"lastname"]]; + _fullName = [self guardJSONElement:[userDictionary objectForKey:@"fullname"]]; + _city = [self guardJSONElement:[userDictionary objectForKey:@"city"]]; + _state = [self guardJSONElement:[userDictionary objectForKey:@"state"]]; + _country = [self guardJSONElement:[userDictionary objectForKey:@"country"]]; + _about = [self guardJSONElement:[userDictionary objectForKey:@"about"]]; + _domain = [self guardJSONElement:[userDictionary objectForKey:@"domain"]]; + _photoCount = [[self guardJSONElement:[userDictionary objectForKey:@"photos_count"]] integerValue]; + _galleriesCount = [[self guardJSONElement:[userDictionary objectForKey:@"galleries_count"]] integerValue]; + _affection = [[self guardJSONElement:[userDictionary objectForKey:@"affection"]] integerValue]; + _friendsCount = [[self guardJSONElement:[userDictionary objectForKey:@"friends_count"]] integerValue]; + _followersCount = [[self guardJSONElement:[userDictionary objectForKey:@"followers_count"]] integerValue]; + _following = [[self guardJSONElement:[userDictionary objectForKey:@"following"]] boolValue]; + _dictionaryRepresentation = userDictionary; + + NSString *urlString = [self guardJSONElement:[userDictionary objectForKey:@"userpic_url"]]; + _userPicURL = urlString ? [NSURL URLWithString:urlString] : nil; + +} + +- (id)guardJSONElement:(id)element +{ + return (element == [NSNull null]) ? nil : element; +} + +@end diff --git a/examples/ASDKgram/Sample/Utilities.h b/examples/ASDKgram/Sample/Utilities.h new file mode 100644 index 0000000000..ce0c589c50 --- /dev/null +++ b/examples/ASDKgram/Sample/Utilities.h @@ -0,0 +1,39 @@ +// +// Utilities.h +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +@interface UIColor (Additions) + ++ (UIColor *)darkBlueColor; ++ (UIColor *)lightBlueColor; + +@end + +@interface UIImage (Additions) + ++ (UIImage *)followingButtonStretchableImageForCornerRadius:(CGFloat)cornerRadius following:(BOOL)followingEnabled; ++ (void)downloadImageForURL:(NSURL *)url completion:(void (^)(UIImage *))block; + +- (UIImage *)makeCircularImageWithSize:(CGSize)size; + +@end + +@interface NSString (Additions) + +// returns a user friendly elapsed time such as '50s', '6m' or '3w' ++ (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString; + +@end + +@interface NSAttributedString (Additions) + ++ (NSAttributedString *)attributedStringWithString:(NSString *)string + fontSize:(CGFloat)size + color:(UIColor *)color + firstWordColor:(UIColor *)firstWordColor; + +@end \ No newline at end of file diff --git a/examples/ASDKgram/Sample/Utilities.m b/examples/ASDKgram/Sample/Utilities.m new file mode 100644 index 0000000000..9f25c1975b --- /dev/null +++ b/examples/ASDKgram/Sample/Utilities.m @@ -0,0 +1,225 @@ +// +// Utilities.m +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "Utilities.h" + +#define StrokeRoundedImages 0 + +@implementation UIColor (Additions) + ++ (UIColor *)darkBlueColor +{ + return [UIColor colorWithRed:70.0/255.0 green:102.0/255.0 blue:118.0/255.0 alpha:1.0]; +} + ++ (UIColor *)lightBlueColor +{ + return [UIColor colorWithRed:70.0/255.0 green:165.0/255.0 blue:196.0/255.0 alpha:1.0]; +} + +@end + +@implementation UIImage (Additions) + ++ (UIImage *)followingButtonStretchableImageForCornerRadius:(CGFloat)cornerRadius following:(BOOL)followingEnabled +{ + CGSize unstretchedSize = CGSizeMake(2 * cornerRadius + 1, 2 * cornerRadius + 1); + CGRect rect = (CGRect) {CGPointZero, unstretchedSize}; + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius]; + + // create a graphics context for the following status button + UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0); + + [path addClip]; + + if (followingEnabled) { + + [[UIColor whiteColor] setFill]; + [path fill]; + + path.lineWidth = 3; + [[UIColor lightBlueColor] setStroke]; + [path stroke]; + + } else { + + [[UIColor lightBlueColor] setFill]; + [path fill]; + } + + UIImage *followingBtnImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + UIImage *followingBtnImageStretchable = [followingBtnImage stretchableImageWithLeftCapWidth:cornerRadius + topCapHeight:cornerRadius]; + return followingBtnImageStretchable; +} + ++ (void)downloadImageForURL:(NSURL *)url completion:(void (^)(UIImage *))block +{ + static NSCache *simpleImageCache = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + simpleImageCache = [[NSCache alloc] init]; + simpleImageCache.countLimit = 10; + }); + + if (!block) { + return; + } + + // check if image is cached + UIImage *image = [simpleImageCache objectForKey:url]; + if (image) { + dispatch_async(dispatch_get_main_queue(), ^{ + block(image); + }); + } else { + // else download image + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; + NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if (data) { + UIImage *image = [UIImage imageWithData:data]; + dispatch_async(dispatch_get_main_queue(), ^{ + block(image); + }); + } + }]; + [task resume]; + } +} + +- (UIImage *)makeCircularImageWithSize:(CGSize)size +{ + // make a CGRect with the image's size + CGRect circleRect = (CGRect) {CGPointZero, size}; + + // begin the image context since we're not in a drawRect: + UIGraphicsBeginImageContextWithOptions(circleRect.size, NO, 0); + + // create a UIBezierPath circle + UIBezierPath *circle = [UIBezierPath bezierPathWithRoundedRect:circleRect cornerRadius:circleRect.size.width/2]; + + // clip to the circle + [circle addClip]; + + // draw the image in the circleRect *AFTER* the context is clipped + [self drawInRect:circleRect]; + + // create a border (for white background pictures) +#if StrokeRoundedImages + circle.lineWidth = 1; + [[UIColor darkGrayColor] set]; + [circle stroke]; +#endif + + // get an image from the image context + UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext(); + + // end the image context since we're not in a drawRect: + UIGraphicsEndImageContext(); + + return roundedImage; +} + +@end + +@implementation NSString (Additions) + +// Returns a user-visible date time string that corresponds to the +// specified RFC 3339 date time string. Note that this does not handle +// all possible RFC 3339 date time strings, just one of the most common +// styles. ++ (NSDate *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString +{ + NSDateFormatter * rfc3339DateFormatter; + NSLocale * enUSPOSIXLocale; + + // Convert the RFC 3339 date time string to an NSDate. + + rfc3339DateFormatter = [[NSDateFormatter alloc] init]; + + enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + + [rfc3339DateFormatter setLocale:enUSPOSIXLocale]; + [rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ'"]; + [rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + return [rfc3339DateFormatter dateFromString:rfc3339DateTimeString]; +} + ++ (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString +{ + // early return if no post date string + if (!uploadDateString) + { + return @"NO POST DATE"; + } + + NSDate *postDate = [self userVisibleDateTimeStringForRFC3339DateTimeString:uploadDateString]; + + if (!postDate) { + return @"DATE CONVERSION ERROR"; + } + + NSDate *currentDate = [NSDate date]; + + NSCalendar *calendar = [NSCalendar currentCalendar]; + + NSUInteger seconds = [[calendar components:NSCalendarUnitSecond fromDate:postDate toDate:currentDate options:0] second]; + NSUInteger minutes = [[calendar components:NSCalendarUnitMinute fromDate:postDate toDate:currentDate options:0] minute]; + NSUInteger hours = [[calendar components:NSCalendarUnitHour fromDate:postDate toDate:currentDate options:0] hour]; + NSUInteger days = [[calendar components:NSCalendarUnitDay fromDate:postDate toDate:currentDate options:0] day]; + + NSString *elapsedTime; + + if (days > 7) { + elapsedTime = [NSString stringWithFormat:@"%luw", (long)ceil(days/7.0)]; + } else if (days > 0) { + elapsedTime = [NSString stringWithFormat:@"%lud", (long)days]; + } else if (hours > 0) { + elapsedTime = [NSString stringWithFormat:@"%luh", (long)hours]; + } else if (minutes > 0) { + elapsedTime = [NSString stringWithFormat:@"%lum", (long)minutes]; + } else if (seconds > 0) { + elapsedTime = [NSString stringWithFormat:@"%lus", (long)seconds]; + } else if (seconds == 0) { + elapsedTime = @"1s"; + } else { + elapsedTime = @"ERROR"; + } + + return elapsedTime; +} + +@end + +@implementation NSAttributedString (Additions) + ++ (NSAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size + color:(nullable UIColor *)color firstWordColor:(nullable UIColor *)firstWordColor +{ + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; + + if (string) { + NSDictionary *attributes = @{NSForegroundColorAttributeName: color ? : [UIColor blackColor], + NSFontAttributeName: [UIFont systemFontOfSize:size]}; + attributedString = [[NSMutableAttributedString alloc] initWithString:string]; + [attributedString addAttributes:attributes range:NSMakeRange(0, string.length)]; + + if (firstWordColor) { + NSRange firstSpaceRange = [string rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + NSRange firstWordRange = NSMakeRange(0, firstSpaceRange.location); + [attributedString addAttribute:NSForegroundColorAttributeName value:firstWordColor range:firstWordRange]; + } + } + + return attributedString; +} + +@end diff --git a/examples/ASDKgram/Sample/WindowWithStatusBarUnderlay.h b/examples/ASDKgram/Sample/WindowWithStatusBarUnderlay.h new file mode 100644 index 0000000000..bb3af4ce7e --- /dev/null +++ b/examples/ASDKgram/Sample/WindowWithStatusBarUnderlay.h @@ -0,0 +1,13 @@ +// +// WindowWithStatusBarUnderlay.h +// Sample +// +// Created by Hannah Troisi on 4/10/16. +// Copyright © 2016 Facebook. All rights reserved. +// + + +// this subclass is neccessary to make the status bar have an opaque, colored background +@interface WindowWithStatusBarUnderlay : UIWindow + +@end diff --git a/examples/ASDKgram/Sample/WindowWithStatusBarUnderlay.m b/examples/ASDKgram/Sample/WindowWithStatusBarUnderlay.m new file mode 100644 index 0000000000..3e8f5a46b7 --- /dev/null +++ b/examples/ASDKgram/Sample/WindowWithStatusBarUnderlay.m @@ -0,0 +1,40 @@ +// +// WindowWithStatusBarUnderlay.m +// Sample +// +// Created by Hannah Troisi on 4/10/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "WindowWithStatusBarUnderlay.h" +#import "Utilities.h" + +@implementation WindowWithStatusBarUnderlay +{ + UIView *_statusBarOpaqueUnderlayView; +} + +-(instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + _statusBarOpaqueUnderlayView = [[UIView alloc] init]; + _statusBarOpaqueUnderlayView.backgroundColor = [UIColor darkBlueColor]; + [self addSubview:_statusBarOpaqueUnderlayView]; + } + return self; +} + +-(void)layoutSubviews +{ + [super layoutSubviews]; + + [self bringSubviewToFront:_statusBarOpaqueUnderlayView]; + + CGRect statusBarFrame = CGRectZero; + statusBarFrame.size.width = [[UIScreen mainScreen] bounds].size.width; + statusBarFrame.size.height = [[UIApplication sharedApplication] statusBarFrame].size.height; + _statusBarOpaqueUnderlayView.frame = statusBarFrame; +} + +@end diff --git a/examples/ASDKgram/Sample/main.m b/examples/ASDKgram/Sample/main.m new file mode 100644 index 0000000000..97769ec676 --- /dev/null +++ b/examples/ASDKgram/Sample/main.m @@ -0,0 +1,14 @@ +// main.m +// ASDKgram +// +// Created by Hannah Troisi on 2/16/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/ASDKgram/Sample/tabBarIcons/camera.png b/examples/ASDKgram/Sample/tabBarIcons/camera.png new file mode 100644 index 0000000000..2eeecba825 Binary files /dev/null and b/examples/ASDKgram/Sample/tabBarIcons/camera.png differ diff --git a/examples/ASDKgram/Sample/tabBarIcons/camera@2x.png b/examples/ASDKgram/Sample/tabBarIcons/camera@2x.png new file mode 100644 index 0000000000..c1ea4ab857 Binary files /dev/null and b/examples/ASDKgram/Sample/tabBarIcons/camera@2x.png differ diff --git a/examples/ASDKgram/Sample/tabBarIcons/cameraRaw.png b/examples/ASDKgram/Sample/tabBarIcons/cameraRaw.png new file mode 100644 index 0000000000..dbf13aa13d Binary files /dev/null and b/examples/ASDKgram/Sample/tabBarIcons/cameraRaw.png differ diff --git a/examples/ASDKgram/Sample/tabBarIcons/earth.png b/examples/ASDKgram/Sample/tabBarIcons/earth.png new file mode 100644 index 0000000000..c182ea5565 Binary files /dev/null and b/examples/ASDKgram/Sample/tabBarIcons/earth.png differ diff --git a/examples/ASDKgram/Sample/tabBarIcons/earth@2x.png b/examples/ASDKgram/Sample/tabBarIcons/earth@2x.png new file mode 100644 index 0000000000..b8049a5004 Binary files /dev/null and b/examples/ASDKgram/Sample/tabBarIcons/earth@2x.png differ diff --git a/examples/ASDKgram/Sample/tabBarIcons/home.png b/examples/ASDKgram/Sample/tabBarIcons/home.png new file mode 100644 index 0000000000..b88cd66a4b Binary files /dev/null and b/examples/ASDKgram/Sample/tabBarIcons/home.png differ diff --git a/examples/ASDKgram/Sample/tabBarIcons/home@2x.png b/examples/ASDKgram/Sample/tabBarIcons/home@2x.png new file mode 100644 index 0000000000..838e660097 Binary files /dev/null and b/examples/ASDKgram/Sample/tabBarIcons/home@2x.png differ diff --git a/examples/ASDKgram/Sample/tabBarIcons/homeRaw.png b/examples/ASDKgram/Sample/tabBarIcons/homeRaw.png new file mode 100644 index 0000000000..09aa24c157 Binary files /dev/null and b/examples/ASDKgram/Sample/tabBarIcons/homeRaw.png differ diff --git a/examples/ASDKgram/Sample/tabBarIcons/profile.png b/examples/ASDKgram/Sample/tabBarIcons/profile.png new file mode 100644 index 0000000000..d885b3aedf Binary files /dev/null and b/examples/ASDKgram/Sample/tabBarIcons/profile.png differ diff --git a/examples/ASDKgram/Sample/tabBarIcons/profile@2x.png b/examples/ASDKgram/Sample/tabBarIcons/profile@2x.png new file mode 100644 index 0000000000..81352fe0cb Binary files /dev/null and b/examples/ASDKgram/Sample/tabBarIcons/profile@2x.png differ diff --git a/examples/ASDKgram/Sample/tabBarIcons/profileRaw.png b/examples/ASDKgram/Sample/tabBarIcons/profileRaw.png new file mode 100644 index 0000000000..0d2894d0ab Binary files /dev/null and b/examples/ASDKgram/Sample/tabBarIcons/profileRaw.png differ diff --git a/examples/ASDKgram/tabBarIcons/camera.png b/examples/ASDKgram/tabBarIcons/camera.png new file mode 100644 index 0000000000..2eeecba825 Binary files /dev/null and b/examples/ASDKgram/tabBarIcons/camera.png differ diff --git a/examples/ASDKgram/tabBarIcons/camera@2x.png b/examples/ASDKgram/tabBarIcons/camera@2x.png new file mode 100644 index 0000000000..c1ea4ab857 Binary files /dev/null and b/examples/ASDKgram/tabBarIcons/camera@2x.png differ diff --git a/examples/ASDKgram/tabBarIcons/cameraRaw.png b/examples/ASDKgram/tabBarIcons/cameraRaw.png new file mode 100644 index 0000000000..dbf13aa13d Binary files /dev/null and b/examples/ASDKgram/tabBarIcons/cameraRaw.png differ diff --git a/examples/ASDKgram/tabBarIcons/earth.png b/examples/ASDKgram/tabBarIcons/earth.png new file mode 100644 index 0000000000..c182ea5565 Binary files /dev/null and b/examples/ASDKgram/tabBarIcons/earth.png differ diff --git a/examples/ASDKgram/tabBarIcons/earth@2x.png b/examples/ASDKgram/tabBarIcons/earth@2x.png new file mode 100644 index 0000000000..b8049a5004 Binary files /dev/null and b/examples/ASDKgram/tabBarIcons/earth@2x.png differ diff --git a/examples/ASDKgram/tabBarIcons/home.png b/examples/ASDKgram/tabBarIcons/home.png new file mode 100644 index 0000000000..b88cd66a4b Binary files /dev/null and b/examples/ASDKgram/tabBarIcons/home.png differ diff --git a/examples/ASDKgram/tabBarIcons/home@2x.png b/examples/ASDKgram/tabBarIcons/home@2x.png new file mode 100644 index 0000000000..838e660097 Binary files /dev/null and b/examples/ASDKgram/tabBarIcons/home@2x.png differ diff --git a/examples/ASDKgram/tabBarIcons/homeRaw.png b/examples/ASDKgram/tabBarIcons/homeRaw.png new file mode 100644 index 0000000000..09aa24c157 Binary files /dev/null and b/examples/ASDKgram/tabBarIcons/homeRaw.png differ diff --git a/examples/ASDKgram/tabBarIcons/profile.png b/examples/ASDKgram/tabBarIcons/profile.png new file mode 100644 index 0000000000..d885b3aedf Binary files /dev/null and b/examples/ASDKgram/tabBarIcons/profile.png differ diff --git a/examples/ASDKgram/tabBarIcons/profile@2x.png b/examples/ASDKgram/tabBarIcons/profile@2x.png new file mode 100644 index 0000000000..81352fe0cb Binary files /dev/null and b/examples/ASDKgram/tabBarIcons/profile@2x.png differ diff --git a/examples/ASDKgram/tabBarIcons/profileRaw.png b/examples/ASDKgram/tabBarIcons/profileRaw.png new file mode 100644 index 0000000000..0d2894d0ab Binary files /dev/null and b/examples/ASDKgram/tabBarIcons/profileRaw.png differ diff --git a/examples/ASViewController/Podfile b/examples/ASViewController/Podfile new file mode 100644 index 0000000000..840a147de4 --- /dev/null +++ b/examples/ASViewController/Podfile @@ -0,0 +1,3 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '7.1' +pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples/ASViewController/Sample.xcodeproj/project.pbxproj b/examples/ASViewController/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..93791a8a13 --- /dev/null +++ b/examples/ASViewController/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,373 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 4E2788BC6EBB297F39C668B1 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 653E635B8743333C495A3A2B /* libPods.a */; }; + 694993D21C8B334F00491CA5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D11C8B334F00491CA5 /* main.m */; }; + 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D41C8B334F00491CA5 /* AppDelegate.m */; }; + 694993D81C8B334F00491CA5 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D71C8B334F00491CA5 /* ViewController.m */; }; + 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 694993DC1C8B334F00491CA5 /* Assets.xcassets */; }; + 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */; }; + 69DCA5221C8B3D30006FF548 /* DetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5211C8B3D30006FF548 /* DetailViewController.m */; }; + 69DCA5251C8BE01F006FF548 /* DetailRootNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */; }; + 69DCA5281C8BE031006FF548 /* DetailCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5271C8BE031006FF548 /* DetailCellNode.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 2F81D9F66E39A2093332B007 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + 653E635B8743333C495A3A2B /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 694993CD1C8B334F00491CA5 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 694993D11C8B334F00491CA5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 694993D31C8B334F00491CA5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 694993D41C8B334F00491CA5 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 694993D61C8B334F00491CA5 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 694993D71C8B334F00491CA5 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 694993DC1C8B334F00491CA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 694993DF1C8B334F00491CA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 694993E11C8B334F00491CA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 69DCA5201C8B3D30006FF548 /* DetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailViewController.h; sourceTree = ""; }; + 69DCA5211C8B3D30006FF548 /* DetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailViewController.m; sourceTree = ""; }; + 69DCA5231C8BE01F006FF548 /* DetailRootNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailRootNode.h; sourceTree = ""; }; + 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailRootNode.m; sourceTree = ""; }; + 69DCA5261C8BE031006FF548 /* DetailCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailCellNode.h; sourceTree = ""; }; + 69DCA5271C8BE031006FF548 /* DetailCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailCellNode.m; sourceTree = ""; }; + B173413A31BE1896F0666DC6 /* 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 */ + 694993CA1C8B334F00491CA5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E2788BC6EBB297F39C668B1 /* libPods.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0DFDB4376BA084DAC7C1976E /* Pods */ = { + isa = PBXGroup; + children = ( + B173413A31BE1896F0666DC6 /* Pods.debug.xcconfig */, + 2F81D9F66E39A2093332B007 /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 478C8D7C412DCBDFE14640D8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 653E635B8743333C495A3A2B /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 694993C41C8B334F00491CA5 = { + isa = PBXGroup; + children = ( + 694993CF1C8B334F00491CA5 /* Sample */, + 694993CE1C8B334F00491CA5 /* Products */, + 0DFDB4376BA084DAC7C1976E /* Pods */, + 478C8D7C412DCBDFE14640D8 /* Frameworks */, + ); + sourceTree = ""; + }; + 694993CE1C8B334F00491CA5 /* Products */ = { + isa = PBXGroup; + children = ( + 694993CD1C8B334F00491CA5 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 694993CF1C8B334F00491CA5 /* Sample */ = { + isa = PBXGroup; + children = ( + 694993D31C8B334F00491CA5 /* AppDelegate.h */, + 694993D41C8B334F00491CA5 /* AppDelegate.m */, + 694993D61C8B334F00491CA5 /* ViewController.h */, + 694993D71C8B334F00491CA5 /* ViewController.m */, + 69DCA5201C8B3D30006FF548 /* DetailViewController.h */, + 69DCA5211C8B3D30006FF548 /* DetailViewController.m */, + 69DCA5231C8BE01F006FF548 /* DetailRootNode.h */, + 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */, + 69DCA5261C8BE031006FF548 /* DetailCellNode.h */, + 69DCA5271C8BE031006FF548 /* DetailCellNode.m */, + 694993DC1C8B334F00491CA5 /* Assets.xcassets */, + 694993D01C8B334F00491CA5 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 694993D01C8B334F00491CA5 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 694993E11C8B334F00491CA5 /* Info.plist */, + 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */, + 694993D11C8B334F00491CA5 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 694993CC1C8B334F00491CA5 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 694993E41C8B334F00491CA5 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + 80035273449C25F4B2E1454F /* Check Pods Manifest.lock */, + 694993C91C8B334F00491CA5 /* Sources */, + 694993CA1C8B334F00491CA5 /* Frameworks */, + 694993CB1C8B334F00491CA5 /* Resources */, + 06EE2E0ABEB6289D4775A867 /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 694993CD1C8B334F00491CA5 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 694993C51C8B334F00491CA5 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = AsyncDisplayKit; + TargetAttributes = { + 694993CC1C8B334F00491CA5 = { + CreatedOnToolsVersion = 7.2.1; + }; + }; + }; + buildConfigurationList = 694993C81C8B334F00491CA5 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 694993C41C8B334F00491CA5; + productRefGroup = 694993CE1C8B334F00491CA5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 694993CC1C8B334F00491CA5 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 694993CB1C8B334F00491CA5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */, + 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 06EE2E0ABEB6289D4775A867 /* 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; + }; + 80035273449C25F4B2E1454F /* 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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 694993C91C8B334F00491CA5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 694993D81C8B334F00491CA5 /* ViewController.m in Sources */, + 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */, + 694993D21C8B334F00491CA5 /* main.m in Sources */, + 69DCA5221C8B3D30006FF548 /* DetailViewController.m in Sources */, + 69DCA5281C8BE031006FF548 /* DetailCellNode.m in Sources */, + 69DCA5251C8BE01F006FF548 /* DetailRootNode.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 694993DF1C8B334F00491CA5 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 694993E21C8B334F00491CA5 /* 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; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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 = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 694993E31C8B334F00491CA5 /* 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 = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + 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 = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 694993E51C8B334F00491CA5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B173413A31BE1896F0666DC6 /* Pods.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 694993E61C8B334F00491CA5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2F81D9F66E39A2093332B007 /* Pods.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 694993C81C8B334F00491CA5 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 694993E21C8B334F00491CA5 /* Debug */, + 694993E31C8B334F00491CA5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 694993E41C8B334F00491CA5 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 694993E51C8B334F00491CA5 /* Debug */, + 694993E61C8B334F00491CA5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 694993C51C8B334F00491CA5 /* Project object */; +} diff --git a/examples/ASViewController/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/ASViewController/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..c00064c54d --- /dev/null +++ b/examples/ASViewController/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASViewController/Sample/AppDelegate.h b/examples/ASViewController/Sample/AppDelegate.h new file mode 100644 index 0000000000..db4bc0a921 --- /dev/null +++ b/examples/ASViewController/Sample/AppDelegate.h @@ -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 + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/examples/ASViewController/Sample/AppDelegate.m b/examples/ASViewController/Sample/AppDelegate.m new file mode 100644 index 0000000000..82398fb14a --- /dev/null +++ b/examples/ASViewController/Sample/AppDelegate.m @@ -0,0 +1,30 @@ +/* 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" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[ViewController new]]; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/ASViewController/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/ASViewController/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..118c98f746 --- /dev/null +++ b/examples/ASViewController/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASViewController/Sample/Base.lproj/LaunchScreen.storyboard b/examples/ASViewController/Sample/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..90d6157f11 --- /dev/null +++ b/examples/ASViewController/Sample/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASViewController/Sample/DetailCellNode.h b/examples/ASViewController/Sample/DetailCellNode.h new file mode 100644 index 0000000000..b3a1a15214 --- /dev/null +++ b/examples/ASViewController/Sample/DetailCellNode.h @@ -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 "ASCellNode.h" + +@class ASNetworkImageNode; + +@interface DetailCellNode : ASCellNode +@property (nonatomic, assign) NSInteger row; +@property (nonatomic, copy) NSString *imageCategory; +@property (nonatomic, strong) ASNetworkImageNode *imageNode; +@end diff --git a/examples/ASViewController/Sample/DetailCellNode.m b/examples/ASViewController/Sample/DetailCellNode.m new file mode 100644 index 0000000000..a5e99653ea --- /dev/null +++ b/examples/ASViewController/Sample/DetailCellNode.m @@ -0,0 +1,60 @@ +/* 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 "DetailCellNode.h" +#import + +@implementation DetailCellNode + +#pragma mark - Lifecycle + +- (instancetype)init +{ + self = [super init]; + if (self == nil) { return self; } + + _imageNode = [[ASNetworkImageNode alloc] init]; + _imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); + [self addSubnode:_imageNode]; + + return self; +} + +#pragma mark - ASDisplayNode + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + self.imageNode.position = CGPointZero; + self.imageNode.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(constrainedSize.max); + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[self.imageNode]]; +} + +- (void)layoutDidFinish +{ + [super layoutDidFinish]; + + // In general set URL of ASNetworkImageNode as soon as possible. Ideally in init or a + // view model setter method. + // In this case as we need to know the size of the node the url is set in layoutDidFinish so + // we have the calculatedSize available + self.imageNode.URL = [self imageURL]; +} + +#pragma mark - Image + +- (NSURL *)imageURL +{ + CGSize imageSize = self.calculatedSize; + NSString *imageURLString = [NSString stringWithFormat:@"http://lorempixel.com/%ld/%ld/%@/%ld", (NSInteger)imageSize.width, (NSInteger)imageSize.height, self.imageCategory, self.row]; + return [NSURL URLWithString:imageURLString]; +} + +@end diff --git a/examples/ASViewController/Sample/DetailRootNode.h b/examples/ASViewController/Sample/DetailRootNode.h new file mode 100644 index 0000000000..35b87f4cef --- /dev/null +++ b/examples/ASViewController/Sample/DetailRootNode.h @@ -0,0 +1,22 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import "ASDisplayNode.h" + +@class ASCollectionNode; + +@interface DetailRootNode : ASDisplayNode + +@property (nonatomic, strong, readonly) ASCollectionNode *collectionNode; + +- (instancetype)initWithImageCategory:(NSString *)imageCategory; + +@end diff --git a/examples/ASViewController/Sample/DetailRootNode.m b/examples/ASViewController/Sample/DetailRootNode.m new file mode 100644 index 0000000000..7dec4b9534 --- /dev/null +++ b/examples/ASViewController/Sample/DetailRootNode.m @@ -0,0 +1,88 @@ +/* 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 "DetailRootNode.h" +#import + +#import "DetailCellNode.h" + +static const NSInteger kImageHeight = 200; + +@interface DetailRootNode () +@property (nonatomic, copy) NSString *imageCategory; +@property (nonatomic, strong) ASCollectionNode *collectionNode; +@end + +@implementation DetailRootNode + +#pragma mark - Lifecycle + +- (instancetype)initWithImageCategory:(NSString *)imageCategory +{ + self = [super init]; + if (self == nil) { return self; } + + _imageCategory = imageCategory; + + // Create ASCollectionView. We don't have to add it explicitly as subnode as we will set usesImplicitHierarchyManagement to YES + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + _collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:layout]; + _collectionNode.delegate = self; + _collectionNode.dataSource = self; + _collectionNode.backgroundColor = [UIColor whiteColor]; + + // Enable usesImplicitHierarchyManagement so the first time the layout pass of the node is happening all nodes that are referenced + // in layouts within layoutSpecThatFits: will be added automatically + self.usesImplicitHierarchyManagement = YES; + + return self; +} + +- (void)dealloc +{ + _collectionNode.delegate = nil; + _collectionNode.dataSource = nil; +} + +#pragma mark - ASDisplayNode + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + self.collectionNode.position = CGPointZero; + self.collectionNode.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(constrainedSize.max); + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[self.collectionNode]]; +} + +#pragma mark - ASCollectionDataSource + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +{ + return 10; +} + +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath +{ + NSString *imageCategory = self.imageCategory; + return ^{ + DetailCellNode *node = [[DetailCellNode alloc] init]; + node.row = indexPath.row; + node.imageCategory = imageCategory; + return node; + }; +} + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath +{ + CGSize imageSize = CGSizeMake(CGRectGetWidth(collectionView.frame), kImageHeight); + return ASSizeRangeMake(imageSize, imageSize); +} + +@end diff --git a/examples/CustomCollectionView/Sample/SupplementaryNode.h b/examples/ASViewController/Sample/DetailViewController.h similarity index 82% rename from examples/CustomCollectionView/Sample/SupplementaryNode.h rename to examples/ASViewController/Sample/DetailViewController.h index f75c929684..b0a02976b6 100644 --- a/examples/CustomCollectionView/Sample/SupplementaryNode.h +++ b/examples/ASViewController/Sample/DetailViewController.h @@ -9,10 +9,9 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#import +#import "ASViewController.h" +#import "DetailRootNode.h" -@interface SupplementaryNode : ASCellNode - -- (instancetype)initWithText:(NSString *)text; +@interface DetailViewController : ASViewController @end diff --git a/examples/ASViewController/Sample/DetailViewController.m b/examples/ASViewController/Sample/DetailViewController.m new file mode 100644 index 0000000000..9a6577eb0b --- /dev/null +++ b/examples/ASViewController/Sample/DetailViewController.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 "DetailViewController.h" +#import + +#import "DetailRootNode.h" + +@implementation DetailViewController + +#pragma mark - Rotation + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator +{ + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + [self.node.collectionNode.view.collectionViewLayout invalidateLayout]; +} + +@end diff --git a/examples/ASViewController/Sample/Info.plist b/examples/ASViewController/Sample/Info.plist new file mode 100644 index 0000000000..6105445463 --- /dev/null +++ b/examples/ASViewController/Sample/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/ASViewController/Sample/ViewController.h b/examples/ASViewController/Sample/ViewController.h new file mode 100644 index 0000000000..ae8047d1f9 --- /dev/null +++ b/examples/ASViewController/Sample/ViewController.h @@ -0,0 +1,19 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import + +@interface ViewController : ASViewController + + +@end + diff --git a/examples/ASViewController/Sample/ViewController.m b/examples/ASViewController/Sample/ViewController.m new file mode 100644 index 0000000000..09d16e9f0b --- /dev/null +++ b/examples/ASViewController/Sample/ViewController.m @@ -0,0 +1,100 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import "ViewController.h" +#import "ASTableNode.h" + +#import "DetailViewController.h" + +@interface ViewController () +@property (nonatomic, copy) NSArray *imageCategories; +@property (nonatomic, strong, readonly) ASTableNode *tableNode; +@end + +@implementation ViewController + + +#pragma mark - Lifecycle + +- (instancetype)init +{ + self = [super initWithNode:[ASTableNode new]]; + if (self == nil) { return self; } + + _imageCategories = @[@"abstract", @"animals", @"business", @"cats", @"city", @"food", @"nightlife", @"fashion", @"people", @"nature", @"sports", @"technics", @"transport"]; + + return self; +} + +- (void)dealloc +{ + self.tableNode.delegate = nil; + self.tableNode.dataSource = nil; +} + + +#pragma mark - UIViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Image Categories"; + + self.tableNode.delegate = self; + self.tableNode.dataSource = self; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + [self.tableNode.view deselectRowAtIndexPath:self.tableNode.view.indexPathForSelectedRow animated:YES]; +} + + +#pragma mark - Setter / Getter + +- (ASTableNode *)tableNode +{ + return (ASTableNode *)self.node; +} + + +#pragma mark - ASTableDataSource / ASTableDelegate + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return self.imageCategories.count; +} + +- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSString *imageCategory = self.imageCategories[indexPath.row]; + return ^{ + ASTextCellNode *textCellNode = [ASTextCellNode new]; + textCellNode.text = [imageCategory capitalizedString]; + return textCellNode; + }; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSString *imageCategory = self.imageCategories[indexPath.row]; + DetailRootNode *detailRootNode = [[DetailRootNode alloc] initWithImageCategory:imageCategory]; + DetailViewController *detailViewController = [[DetailViewController alloc] initWithNode:detailRootNode]; + detailViewController.title = [imageCategory capitalizedString]; + [self.navigationController pushViewController:detailViewController animated:YES]; + + +} + +@end diff --git a/examples/ASViewController/Sample/main.m b/examples/ASViewController/Sample/main.m new file mode 100644 index 0000000000..6559d60028 --- /dev/null +++ b/examples/ASViewController/Sample/main.m @@ -0,0 +1,19 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/CarthageBuildTest/Cartfile b/examples/CarthageBuildTest/Cartfile new file mode 100644 index 0000000000..75ef551216 --- /dev/null +++ b/examples/CarthageBuildTest/Cartfile @@ -0,0 +1 @@ +git "file:///build.sh/will/put/local/absolute/path/here" "master" diff --git a/examples/CarthageBuildTest/CarthageExample/AppDelegate.h b/examples/CarthageBuildTest/CarthageExample/AppDelegate.h new file mode 100644 index 0000000000..6f3bc417f3 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/AppDelegate.h @@ -0,0 +1,17 @@ +// +// AppDelegate.h +// CarthageExample +// +// Created by Engin Kurutepe on 23/02/16. +// Copyright © 2016 Engin Kurutepe. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/examples/CarthageBuildTest/CarthageExample/AppDelegate.m b/examples/CarthageBuildTest/CarthageExample/AppDelegate.m new file mode 100644 index 0000000000..225a9f70b1 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/AppDelegate.m @@ -0,0 +1,47 @@ +// +// AppDelegate.m +// CarthageExample +// +// Created by Engin Kurutepe on 23/02/16. +// Copyright © 2016 Engin Kurutepe. All rights reserved. +// + +@import AsyncDisplayKit; + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/examples/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..118c98f746 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CarthageBuildTest/CarthageExample/Base.lproj/LaunchScreen.storyboard b/examples/CarthageBuildTest/CarthageExample/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..2e721e1833 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/CarthageBuildTest/CarthageExample/Base.lproj/Main.storyboard b/examples/CarthageBuildTest/CarthageExample/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..f56d2f3bb5 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/Base.lproj/Main.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/CarthageBuildTest/CarthageExample/Info.plist b/examples/CarthageBuildTest/CarthageExample/Info.plist new file mode 100644 index 0000000000..6905cc67bb --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/CarthageBuildTest/CarthageExample/ViewController.h b/examples/CarthageBuildTest/CarthageExample/ViewController.h new file mode 100644 index 0000000000..4cf7ecbc3e --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/ViewController.h @@ -0,0 +1,15 @@ +// +// ViewController.h +// CarthageExample +// +// Created by Engin Kurutepe on 23/02/16. +// Copyright © 2016 Engin Kurutepe. All rights reserved. +// + +#import + +@interface ViewController : UIViewController + + +@end + diff --git a/examples/CarthageBuildTest/CarthageExample/ViewController.m b/examples/CarthageBuildTest/CarthageExample/ViewController.m new file mode 100644 index 0000000000..a65c7f7d6e --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/ViewController.m @@ -0,0 +1,36 @@ +// +// ViewController.m +// CarthageExample +// +// Created by Engin Kurutepe on 23/02/16. +// Copyright © 2016 Engin Kurutepe. All rights reserved. +// + +@import AsyncDisplayKit; + +#import "ViewController.h" + +@interface ViewController () + +@end + +@implementation ViewController + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + CGSize screenSize = self.view.bounds.size; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + ASTextNode *node = [[ASTextNode alloc] init]; + node.attributedString = [[NSAttributedString alloc] initWithString:@"hello world"]; + [node measure:(CGSize){.width = screenSize.width, .height = CGFLOAT_MAX}]; + node.frame = (CGRect) {.origin = (CGPoint){.x = 100, .y = 100}, .size = node.calculatedSize }; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.view addSubview:node.view]; + }); + }); +} + +@end diff --git a/examples/CarthageBuildTest/CarthageExample/main.m b/examples/CarthageBuildTest/CarthageExample/main.m new file mode 100644 index 0000000000..e37b278758 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/main.m @@ -0,0 +1,16 @@ +// +// main.m +// CarthageExample +// +// Created by Engin Kurutepe on 23/02/16. +// Copyright © 2016 Engin Kurutepe. All rights reserved. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/CarthageBuildTest/README.md b/examples/CarthageBuildTest/README.md new file mode 100644 index 0000000000..65ad6a6737 --- /dev/null +++ b/examples/CarthageBuildTest/README.md @@ -0,0 +1,7 @@ +This project is supposed to test that the `AsyncDisplayKit.framework` built by Carthage from the master branch can be imported as a module without causing any warnings and errors. + +Steps to verify: + +- Run `carthage update --platform iOS` +- Build `CarthageExample.xcodeproj` +- Verify that there are 0 Errors and 0 Warnings diff --git a/examples/CarthageBuildTest/Sample.xcodeproj/project.pbxproj b/examples/CarthageBuildTest/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..cffd86f348 --- /dev/null +++ b/examples/CarthageBuildTest/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,349 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 871BB34E1C7C98B1005CF62A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 871BB34D1C7C98B1005CF62A /* main.m */; }; + 871BB3511C7C98B1005CF62A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 871BB3501C7C98B1005CF62A /* AppDelegate.m */; }; + 871BB3541C7C98B1005CF62A /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 871BB3531C7C98B1005CF62A /* ViewController.m */; }; + 871BB3571C7C98B1005CF62A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 871BB3551C7C98B1005CF62A /* Main.storyboard */; }; + 871BB3591C7C98B1005CF62A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 871BB3581C7C98B1005CF62A /* Assets.xcassets */; }; + 871BB35C1C7C98B1005CF62A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 871BB35A1C7C98B1005CF62A /* LaunchScreen.storyboard */; }; + 871BB3651C7C99B0005CF62A /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 871BB3641C7C99B0005CF62A /* AsyncDisplayKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 871BB3491C7C98B1005CF62A /* CarthageExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CarthageExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 871BB34D1C7C98B1005CF62A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 871BB34F1C7C98B1005CF62A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 871BB3501C7C98B1005CF62A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 871BB3521C7C98B1005CF62A /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 871BB3531C7C98B1005CF62A /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 871BB3561C7C98B1005CF62A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 871BB3581C7C98B1005CF62A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 871BB35B1C7C98B1005CF62A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 871BB35D1C7C98B1005CF62A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 871BB3641C7C99B0005CF62A /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AsyncDisplayKit.framework; path = Carthage/Build/iOS/AsyncDisplayKit.framework; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 871BB3461C7C98B1005CF62A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 871BB3651C7C99B0005CF62A /* AsyncDisplayKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 871BB3401C7C98B1005CF62A = { + isa = PBXGroup; + children = ( + 871BB34B1C7C98B1005CF62A /* CarthageExample */, + 871BB34A1C7C98B1005CF62A /* Products */, + ); + sourceTree = ""; + }; + 871BB34A1C7C98B1005CF62A /* Products */ = { + isa = PBXGroup; + children = ( + 871BB3491C7C98B1005CF62A /* CarthageExample.app */, + ); + name = Products; + sourceTree = ""; + }; + 871BB34B1C7C98B1005CF62A /* CarthageExample */ = { + isa = PBXGroup; + children = ( + 871BB34F1C7C98B1005CF62A /* AppDelegate.h */, + 871BB3501C7C98B1005CF62A /* AppDelegate.m */, + 871BB3581C7C98B1005CF62A /* Assets.xcassets */, + 871BB3631C7C9994005CF62A /* Frameworks */, + 871BB35D1C7C98B1005CF62A /* Info.plist */, + 871BB35A1C7C98B1005CF62A /* LaunchScreen.storyboard */, + 871BB3551C7C98B1005CF62A /* Main.storyboard */, + 871BB34C1C7C98B1005CF62A /* Supporting Files */, + 871BB3521C7C98B1005CF62A /* ViewController.h */, + 871BB3531C7C98B1005CF62A /* ViewController.m */, + ); + path = CarthageExample; + sourceTree = ""; + }; + 871BB34C1C7C98B1005CF62A /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 871BB34D1C7C98B1005CF62A /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 871BB3631C7C9994005CF62A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 871BB3641C7C99B0005CF62A /* AsyncDisplayKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 871BB3481C7C98B1005CF62A /* CarthageExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 871BB3601C7C98B1005CF62A /* Build configuration list for PBXNativeTarget "CarthageExample" */; + buildPhases = ( + 871BB3451C7C98B1005CF62A /* Sources */, + 871BB3461C7C98B1005CF62A /* Frameworks */, + 871BB3471C7C98B1005CF62A /* Resources */, + 871BB3661C7C99B8005CF62A /* Copy Framworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CarthageExample; + productName = CarthageExample; + productReference = 871BB3491C7C98B1005CF62A /* CarthageExample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 871BB3411C7C98B1005CF62A /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = "Engin Kurutepe"; + TargetAttributes = { + 871BB3481C7C98B1005CF62A = { + CreatedOnToolsVersion = 7.2.1; + }; + }; + }; + buildConfigurationList = 871BB3441C7C98B1005CF62A /* Build configuration list for PBXProject "CarthageExample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 871BB3401C7C98B1005CF62A; + productRefGroup = 871BB34A1C7C98B1005CF62A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 871BB3481C7C98B1005CF62A /* CarthageExample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 871BB3471C7C98B1005CF62A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 871BB35C1C7C98B1005CF62A /* LaunchScreen.storyboard in Resources */, + 871BB3591C7C98B1005CF62A /* Assets.xcassets in Resources */, + 871BB3571C7C98B1005CF62A /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 871BB3661C7C99B8005CF62A /* Copy Framworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/Carthage/Build/iOS/AsyncDisplayKit.framework", + ); + name = "Copy Framworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/local/bin/carthage copy-frameworks"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 871BB3451C7C98B1005CF62A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 871BB3541C7C98B1005CF62A /* ViewController.m in Sources */, + 871BB3511C7C98B1005CF62A /* AppDelegate.m in Sources */, + 871BB34E1C7C98B1005CF62A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 871BB3551C7C98B1005CF62A /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 871BB3561C7C98B1005CF62A /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 871BB35A1C7C98B1005CF62A /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 871BB35B1C7C98B1005CF62A /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 871BB35E1C7C98B1005CF62A /* 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; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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 = 9.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 871BB35F1C7C98B1005CF62A /* 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 = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + 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 = 9.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 871BB3611C7C98B1005CF62A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = CarthageExample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.asyncdisplaykit.CarthageExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 871BB3621C7C98B1005CF62A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = CarthageExample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.asyncdisplaykit.CarthageExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 871BB3441C7C98B1005CF62A /* Build configuration list for PBXProject "CarthageExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 871BB35E1C7C98B1005CF62A /* Debug */, + 871BB35F1C7C98B1005CF62A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 871BB3601C7C98B1005CF62A /* Build configuration list for PBXNativeTarget "CarthageExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 871BB3611C7C98B1005CF62A /* Debug */, + 871BB3621C7C98B1005CF62A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 871BB3411C7C98B1005CF62A /* Project object */; +} diff --git a/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..ef35de35d9 --- /dev/null +++ b/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..08de0be8d3 --- /dev/null +++ b/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + diff --git a/examples/CarthageBuildTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/CarthageBuildTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..8b3132f292 --- /dev/null +++ b/examples/CarthageBuildTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/CatDealsCollectionView/Sample/ViewController.m b/examples/CatDealsCollectionView/Sample/ViewController.m index 31a1e35435..0effcdbdc3 100644 --- a/examples/CatDealsCollectionView/Sample/ViewController.m +++ b/examples/CatDealsCollectionView/Sample/ViewController.m @@ -146,11 +146,6 @@ static const CGFloat kVerticalSectionPadding = 20.0f; [_collectionView.collectionViewLayout invalidateLayout]; } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - - (void)reloadTapped { [_collectionView reloadData]; @@ -159,10 +154,12 @@ static const CGFloat kVerticalSectionPadding = 20.0f; #pragma mark - #pragma mark ASCollectionView data source. -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { ItemViewModel *viewModel = _data[indexPath.item]; - return [[ItemNode alloc] initWithViewModel:viewModel]; + return ^{ + return [[ItemNode alloc] initWithViewModel:viewModel]; + }; } - (ASCellNode *)collectionView:(UICollectionView *)collectionView nodeForSupplementaryElementOfKind:(nonnull NSString *)kind atIndexPath:(nonnull NSIndexPath *)indexPath { diff --git a/examples/CollectionViewWithViewControllerCells/Sample/ViewController.m b/examples/CollectionViewWithViewControllerCells/Sample/ViewController.m index 36f0ef5011..a6613ea11c 100644 --- a/examples/CollectionViewWithViewControllerCells/Sample/ViewController.m +++ b/examples/CollectionViewWithViewControllerCells/Sample/ViewController.m @@ -34,34 +34,21 @@ static NSUInteger kNumberOfImages = 14; - (instancetype)init { - if (!(self = [super init])) - return nil; - - _sections = [NSMutableArray array]; - [_sections addObject:[NSMutableArray array]]; - for (NSUInteger idx = 0, section = 0; idx < kNumberOfImages; idx++) { - NSString *name = [NSString stringWithFormat:@"image_%lu.jpg", (unsigned long)idx]; - [_sections[section] addObject:[UIImage imageNamed:name]]; - if ((idx + 1) % 5 == 0 && idx < kNumberOfImages - 1) { - section++; - [_sections addObject:[NSMutableArray array]]; + self = [super init]; + if (self) { + + _sections = [NSMutableArray array]; + [_sections addObject:[NSMutableArray array]]; + for (NSUInteger idx = 0, section = 0; idx < kNumberOfImages; idx++) { + NSString *name = [NSString stringWithFormat:@"image_%lu.jpg", (unsigned long)idx]; + [_sections[section] addObject:[UIImage imageNamed:name]]; + if ((idx + 1) % 5 == 0 && idx < kNumberOfImages - 1) { + section++; + [_sections addObject:[NSMutableArray array]]; + } } + } - - MosaicCollectionViewLayout *layout = [[MosaicCollectionViewLayout alloc] init]; - layout.numberOfColumns = 2; - layout.headerHeight = 44.0; - - _layoutInspector = [[MosaicCollectionViewLayoutInspector alloc] init]; - - _collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:YES]; - _collectionView.asyncDataSource = self; - _collectionView.asyncDelegate = self; - _collectionView.layoutInspector = _layoutInspector; - _collectionView.backgroundColor = [UIColor whiteColor]; - - [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; - return self; } @@ -69,17 +56,27 @@ static NSUInteger kNumberOfImages = 14; { [super viewDidLoad]; + MosaicCollectionViewLayout *layout = [[MosaicCollectionViewLayout alloc] init]; + layout.numberOfColumns = 2; + layout.headerHeight = 44.0; + + _layoutInspector = [[MosaicCollectionViewLayoutInspector alloc] init]; + + _collectionView = [[ASCollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; + _collectionView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + _collectionView.asyncDataSource = self; + _collectionView.asyncDelegate = self; + _collectionView.layoutInspector = _layoutInspector; + _collectionView.backgroundColor = [UIColor whiteColor]; + + [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; [self.view addSubview:_collectionView]; } -- (void)viewWillLayoutSubviews +- (void)dealloc { - _collectionView.frame = self.view.bounds; -} - -- (BOOL)prefersStatusBarHidden -{ - return YES; + _collectionView.asyncDataSource = nil; + _collectionView.asyncDelegate = nil; } - (void)reloadTapped @@ -90,16 +87,17 @@ static NSUInteger kNumberOfImages = 14; #pragma mark - #pragma mark ASCollectionView data source. -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { - ASCellNode *node = [[ASCellNode alloc] initWithViewControllerBlock:^UIViewController *{ - return [[ImageViewController alloc] initWithImage:_sections[indexPath.section][indexPath.item]]; - } didLoadBlock:nil]; - - node.layer.borderWidth = 1.0; - node.layer.borderColor = [UIColor blackColor].CGColor; - - return node; + UIImage *image = _sections[indexPath.section][indexPath.item]; + return ^{ + return [[ASCellNode alloc] initWithViewControllerBlock:^UIViewController *{ + return [[ImageViewController alloc] initWithImage:image]; + } didLoadBlock:^(ASDisplayNode * _Nonnull node) { + node.layer.borderWidth = 1.0; + node.layer.borderColor = [UIColor blackColor].CGColor; + }]; + }; } - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath @@ -108,11 +106,13 @@ static NSUInteger kNumberOfImages = 14; return [[SupplementaryNode alloc] initWithText:text]; } -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView +{ return _sections.count; } -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +{ return [_sections[section] count]; } diff --git a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj index 88ca8858e3..70cc92d2d3 100644 --- a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 25A1FA851C02F7AC00193875 /* MosaicCollectionViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */; }; 25A1FA881C02FCB000193875 /* ImageCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A1FA871C02FCB000193875 /* ImageCellNode.m */; }; - 9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */; }; 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; }; AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; }; AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; }; @@ -24,8 +23,6 @@ 25A1FA861C02FCB000193875 /* ImageCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageCellNode.h; sourceTree = ""; }; 25A1FA871C02FCB000193875 /* ImageCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageCellNode.m; sourceTree = ""; }; 2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; - 9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SupplementaryNode.h; sourceTree = ""; }; - 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SupplementaryNode.m; sourceTree = ""; }; 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Launchboard.storyboard; sourceTree = ""; }; AC3C4A5E1A11F47200143C57 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; AC3C4A621A11F47200143C57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -91,8 +88,6 @@ 25A1FA871C02FCB000193875 /* ImageCellNode.m */, AC3C4A8D1A11F80C00143C57 /* Images.xcassets */, AC3C4A611A11F47200143C57 /* Supporting Files */, - 9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */, - 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */, ); indentWidth = 2; path = Sample; @@ -224,7 +219,6 @@ files = ( 25A1FA851C02F7AC00193875 /* MosaicCollectionViewLayout.m in Sources */, AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */, - 9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */, AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */, AC3C4A641A11F47200143C57 /* main.m in Sources */, 25A1FA881C02FCB000193875 /* ImageCellNode.m in Sources */, diff --git a/examples/CustomCollectionView/Sample/ImageCellNode.m b/examples/CustomCollectionView/Sample/ImageCellNode.m index 3bb74a142d..02064168c7 100644 --- a/examples/CustomCollectionView/Sample/ImageCellNode.m +++ b/examples/CustomCollectionView/Sample/ImageCellNode.m @@ -31,6 +31,8 @@ - (void)layout { + [super layout]; + _imageNode.frame = CGRectMake(0, 0, _imageNode.calculatedSize.width, _imageNode.calculatedSize.height); } diff --git a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m index 0e2c65d027..67654b0df9 100644 --- a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m +++ b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m @@ -119,7 +119,7 @@ - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { - if (!CGRectEqualToRect(self.collectionView.bounds, newBounds)) { + if (!CGSizeEqualToSize(self.collectionView.bounds.size, newBounds.size)) { return YES; } return NO; diff --git a/examples/CustomCollectionView/Sample/SupplementaryNode.m b/examples/CustomCollectionView/Sample/SupplementaryNode.m deleted file mode 100644 index 76ba17b4b6..0000000000 --- a/examples/CustomCollectionView/Sample/SupplementaryNode.m +++ /dev/null @@ -1,52 +0,0 @@ -/* This file provided by Facebook is for non-commercial testing and evaluation - * purposes only. Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "SupplementaryNode.h" - -#import -#import -#import - -@implementation SupplementaryNode { - ASTextNode *_textNode; -} - -- (instancetype)initWithText:(NSString *)text -{ - self = [super init]; - if (self != nil) { - _textNode = [[ASTextNode alloc] init]; - _textNode.attributedString = [[NSAttributedString alloc] initWithString:text - attributes:[self textAttributes]]; - [self addSubnode:_textNode]; - } - return self; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - ASCenterLayoutSpec *center = [[ASCenterLayoutSpec alloc] init]; - center.centeringOptions = ASCenterLayoutSpecCenteringY; - center.child = _textNode; - return center; -} - -#pragma mark - Text Formatting - -- (NSDictionary *)textAttributes -{ - return @{ - NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline], - NSForegroundColorAttributeName: [UIColor grayColor], - }; -} - -@end diff --git a/examples/CustomCollectionView/Sample/ViewController.m b/examples/CustomCollectionView/Sample/ViewController.m index f8b6071bad..76beec0259 100644 --- a/examples/CustomCollectionView/Sample/ViewController.m +++ b/examples/CustomCollectionView/Sample/ViewController.m @@ -13,7 +13,6 @@ #import #import "MosaicCollectionViewLayout.h" -#import "SupplementaryNode.h" #import "ImageCellNode.h" static NSUInteger kNumberOfImages = 14; @@ -65,6 +64,12 @@ static NSUInteger kNumberOfImages = 14; return self; } +- (void)dealloc +{ + _collectionView.asyncDataSource = nil; + _collectionView.asyncDelegate = nil; +} + - (void)viewDidLoad { [super viewDidLoad]; @@ -77,11 +82,6 @@ static NSUInteger kNumberOfImages = 14; _collectionView.frame = self.view.bounds; } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - - (void)reloadTapped { [_collectionView reloadData]; @@ -90,15 +90,25 @@ static NSUInteger kNumberOfImages = 14; #pragma mark - #pragma mark ASCollectionView data source. -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { - return [[ImageCellNode alloc] initWithImage:_sections[indexPath.section][indexPath.item]]; + UIImage *image = _sections[indexPath.section][indexPath.item]; + return ^{ + return [[ImageCellNode alloc] initWithImage:image]; + }; } + - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - NSString *text = [NSString stringWithFormat:@"Section %d", (int)indexPath.section + 1]; - return [[SupplementaryNode alloc] initWithText:text]; + NSDictionary *textAttributes = @{ + NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline], + NSForegroundColorAttributeName: [UIColor grayColor] + }; + UIEdgeInsets textInsets = UIEdgeInsetsMake(11.0, 0, 11.0, 0); + ASTextCellNode *textCellNode = [[ASTextCellNode alloc] initWithAttributes:textAttributes insets:textInsets]; + textCellNode.text = [NSString stringWithFormat:@"Section %zd", indexPath.section + 1]; + return textCellNode; } - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView diff --git a/examples/EditableText/CustomCollectionView.gif b/examples/EditableText/CustomCollectionView.gif new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/EditableText/Sample.xcodeproj/project.pbxproj b/examples/EditableText/Sample.xcodeproj/project.pbxproj index a1a1bd3d35..3440a43995 100644 --- a/examples/EditableText/Sample.xcodeproj/project.pbxproj +++ b/examples/EditableText/Sample.xcodeproj/project.pbxproj @@ -1,345 +1,726 @@ -// !$*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 */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - usesTabs = 0; - }; - 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; - 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; - 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 */; -} + + + + + archiveVersion + 1 + classes + + objectVersion + 46 + objects + + 0585427F19D4DBE100606EA6 + + isa + PBXFileReference + lastKnownFileType + image.png + name + Default-568h@2x.png + path + ../Default-568h@2x.png + sourceTree + <group> + + 0585428019D4DBE100606EA6 + + fileRef + 0585427F19D4DBE100606EA6 + isa + PBXBuildFile + + 05E2127819D4DB510098F589 + + children + + 05E2128319D4DB510098F589 + 05E2128219D4DB510098F589 + 1A943BF0259746F18D6E423F + 1AE410B73DA5C3BD087ACDD7 + + indentWidth + 2 + isa + PBXGroup + sourceTree + <group> + tabWidth + 2 + usesTabs + 0 + + 05E2127919D4DB510098F589 + + attributes + + LastUpgradeCheck + 0600 + ORGANIZATIONNAME + Facebook + TargetAttributes + + 05E2128019D4DB510098F589 + + CreatedOnToolsVersion + 6.0.1 + + + + buildConfigurationList + 05E2127C19D4DB510098F589 + compatibilityVersion + Xcode 3.2 + developmentRegion + English + hasScannedForEncodings + 0 + isa + PBXProject + knownRegions + + en + Base + + mainGroup + 05E2127819D4DB510098F589 + productRefGroup + 05E2128219D4DB510098F589 + projectDirPath + + projectReferences + + projectRoot + + targets + + 05E2128019D4DB510098F589 + + + 05E2127C19D4DB510098F589 + + buildConfigurations + + 05E212A219D4DB510098F589 + 05E212A319D4DB510098F589 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + 05E2127D19D4DB510098F589 + + buildActionMask + 2147483647 + files + + 05E2128D19D4DB510098F589 + 05E2128A19D4DB510098F589 + 05E2128719D4DB510098F589 + + isa + PBXSourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 05E2127E19D4DB510098F589 + + buildActionMask + 2147483647 + files + + 3EC0CDCBA10D483D9F386E5E + + isa + PBXFrameworksBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 05E2127F19D4DB510098F589 + + buildActionMask + 2147483647 + files + + 0585428019D4DBE100606EA6 + 6C2C82AC19EE274300767484 + 6C2C82AD19EE274300767484 + + isa + PBXResourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 05E2128019D4DB510098F589 + + buildConfigurationList + 05E212A419D4DB510098F589 + buildPhases + + E080B80F89C34A25B3488E26 + 05E2127D19D4DB510098F589 + 05E2127E19D4DB510098F589 + 05E2127F19D4DB510098F589 + F012A6F39E0149F18F564F50 + 6FBCCC34F8CCA9B610492536 + + buildRules + + dependencies + + isa + PBXNativeTarget + name + Sample + productName + Sample + productReference + 05E2128119D4DB510098F589 + productType + com.apple.product-type.application + + 05E2128119D4DB510098F589 + + explicitFileType + wrapper.application + includeInIndex + 0 + isa + PBXFileReference + path + Sample.app + sourceTree + BUILT_PRODUCTS_DIR + + 05E2128219D4DB510098F589 + + children + + 05E2128119D4DB510098F589 + + isa + PBXGroup + name + Products + sourceTree + <group> + + 05E2128319D4DB510098F589 + + children + + 05E2128819D4DB510098F589 + 05E2128919D4DB510098F589 + 05E2128B19D4DB510098F589 + 05E2128C19D4DB510098F589 + 05E2128419D4DB510098F589 + + isa + PBXGroup + path + Sample + sourceTree + <group> + + 05E2128419D4DB510098F589 + + children + + 0585427F19D4DBE100606EA6 + 6C2C82AA19EE274300767484 + 6C2C82AB19EE274300767484 + 05E2128519D4DB510098F589 + 05E2128619D4DB510098F589 + + isa + PBXGroup + name + Supporting Files + sourceTree + <group> + + 05E2128519D4DB510098F589 + + isa + PBXFileReference + lastKnownFileType + text.plist.xml + path + Info.plist + sourceTree + <group> + + 05E2128619D4DB510098F589 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + main.m + sourceTree + <group> + + 05E2128719D4DB510098F589 + + fileRef + 05E2128619D4DB510098F589 + isa + PBXBuildFile + + 05E2128819D4DB510098F589 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + AppDelegate.h + sourceTree + <group> + + 05E2128919D4DB510098F589 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + AppDelegate.m + sourceTree + <group> + + 05E2128A19D4DB510098F589 + + fileRef + 05E2128919D4DB510098F589 + isa + PBXBuildFile + + 05E2128B19D4DB510098F589 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + ViewController.h + sourceTree + <group> + + 05E2128C19D4DB510098F589 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + ViewController.m + sourceTree + <group> + + 05E2128D19D4DB510098F589 + + fileRef + 05E2128C19D4DB510098F589 + isa + PBXBuildFile + + 05E212A219D4DB510098F589 + + 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 + + isa + XCBuildConfiguration + name + Debug + + 05E212A319D4DB510098F589 + + 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 + + isa + XCBuildConfiguration + name + Release + + 05E212A419D4DB510098F589 + + buildConfigurations + + 05E212A519D4DB510098F589 + 05E212A619D4DB510098F589 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + 05E212A519D4DB510098F589 + + baseConfigurationReference + C068F1D3F0CC317E895FCDAB + buildSettings + + ASSETCATALOG_COMPILER_APPICON_NAME + AppIcon + INFOPLIST_FILE + Sample/Info.plist + LD_RUNPATH_SEARCH_PATHS + $(inherited) @executable_path/Frameworks + PRODUCT_NAME + $(TARGET_NAME) + + isa + XCBuildConfiguration + name + Debug + + 05E212A619D4DB510098F589 + + baseConfigurationReference + 088AA6578212BE9BFBB07B70 + buildSettings + + ASSETCATALOG_COMPILER_APPICON_NAME + AppIcon + INFOPLIST_FILE + Sample/Info.plist + LD_RUNPATH_SEARCH_PATHS + $(inherited) @executable_path/Frameworks + PRODUCT_NAME + $(TARGET_NAME) + + isa + XCBuildConfiguration + name + Release + + 088AA6578212BE9BFBB07B70 + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + text.xcconfig + name + Pods.release.xcconfig + path + Pods/Target Support Files/Pods/Pods.release.xcconfig + sourceTree + <group> + + 1A943BF0259746F18D6E423F + + children + + 3D24B17D1E4A4E7A9566C5E9 + + isa + PBXGroup + name + Frameworks + sourceTree + <group> + + 1AE410B73DA5C3BD087ACDD7 + + children + + C068F1D3F0CC317E895FCDAB + 088AA6578212BE9BFBB07B70 + + isa + PBXGroup + name + Pods + sourceTree + <group> + + 3D24B17D1E4A4E7A9566C5E9 + + explicitFileType + archive.ar + includeInIndex + 0 + isa + PBXFileReference + path + libPods.a + sourceTree + BUILT_PRODUCTS_DIR + + 3EC0CDCBA10D483D9F386E5E + + fileRef + 3D24B17D1E4A4E7A9566C5E9 + isa + PBXBuildFile + + 6C2C82AA19EE274300767484 + + isa + PBXFileReference + lastKnownFileType + image.png + path + Default-667h@2x.png + sourceTree + SOURCE_ROOT + + 6C2C82AB19EE274300767484 + + isa + PBXFileReference + lastKnownFileType + image.png + path + Default-736h@3x.png + sourceTree + SOURCE_ROOT + + 6C2C82AC19EE274300767484 + + fileRef + 6C2C82AA19EE274300767484 + isa + PBXBuildFile + + 6C2C82AD19EE274300767484 + + fileRef + 6C2C82AB19EE274300767484 + isa + PBXBuildFile + + 6FBCCC34F8CCA9B610492536 + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + Embed Pods Frameworks + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + "${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh" + + showEnvVarsInLog + 0 + + C068F1D3F0CC317E895FCDAB + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + text.xcconfig + name + Pods.debug.xcconfig + path + Pods/Target Support Files/Pods/Pods.debug.xcconfig + sourceTree + <group> + + E080B80F89C34A25B3488E26 + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + Check Pods Manifest.lock + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null +if [[ $? != 0 ]] ; then + cat << EOM +error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. +EOM + exit 1 +fi + + showEnvVarsInLog + 0 + + F012A6F39E0149F18F564F50 + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + Copy Pods Resources + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + "${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh" + + showEnvVarsInLog + 0 + + + rootObject + 05E2127919D4DB510098F589 + + diff --git a/examples/EditableText/Sample.xcworkspace/contents.xcworkspacedata b/examples/EditableText/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/EditableText/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/EditableText/Sample/ViewController.m b/examples/EditableText/Sample/ViewController.m index 91288dfad5..539f045480 100644 --- a/examples/EditableText/Sample/ViewController.m +++ b/examples/EditableText/Sample/ViewController.m @@ -17,6 +17,10 @@ @interface ViewController () { ASEditableTextNode *_textNode; + + // These elements are a test case for ASTextNode truncation. + UILabel *_label; + ASTextNode *_node; } @end @@ -44,6 +48,24 @@ // the usual delegate methods are available; see ASEditableTextNodeDelegate _textNode.delegate = self; + + + // Do any additional setup after loading the view, typically from a nib. + NSDictionary *attrs = @{ NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue" size:12.0f] }; + NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"1\n2\n3\n4\n5" attributes:attrs]; + + _label = [[UILabel alloc] init]; + _label.attributedText = string; + _label.backgroundColor = [UIColor lightGrayColor]; + _label.numberOfLines = 3; + _label.frame = CGRectMake(20, 400, 40, 100); + + _node = [[ASTextNode alloc] init]; + _node.maximumNumberOfLines = 3; + _node.backgroundColor = [UIColor lightGrayColor]; + _node.attributedString = string; + _node.frame = CGRectMake(70, 400, 40, 100); +// [_node measure:CGSizeMake(40, 50)]; No longer needed now that https://github.com/facebook/AsyncDisplayKit/issues/1295 is fixed. return self; } @@ -53,6 +75,8 @@ [super viewDidLoad]; [self.view addSubnode:_textNode]; + [self.view addSubnode:_node]; + [self.view addSubview:_label]; [self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]]; } @@ -63,11 +87,6 @@ _textNode.frame = CGRectMake(0, 20, self.view.bounds.size.width, (self.view.bounds.size.height / 2) - 40); } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - - (void)tap:(UITapGestureRecognizer *)sender { // dismiss the keyboard when we tap outside the text field diff --git a/examples/HorizontalWithinVerticalScrolling/Sample/HorizontalScrollCellNode.mm b/examples/HorizontalWithinVerticalScrolling/Sample/HorizontalScrollCellNode.mm index 9eb2ee9b31..15b36b8402 100644 --- a/examples/HorizontalWithinVerticalScrolling/Sample/HorizontalScrollCellNode.mm +++ b/examples/HorizontalWithinVerticalScrolling/Sample/HorizontalScrollCellNode.mm @@ -67,11 +67,13 @@ static const CGFloat kInnerPadding = 10.0f; return 5; } -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { - RandomCoreGraphicsNode *elementNode = [[RandomCoreGraphicsNode alloc] init]; - elementNode.preferredFrameSize = _elementSize; - return elementNode; + return ^{ + RandomCoreGraphicsNode *elementNode = [[RandomCoreGraphicsNode alloc] init]; + elementNode.preferredFrameSize = _elementSize; + return elementNode; + }; } - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize diff --git a/examples/HorizontalWithinVerticalScrolling/Sample/ViewController.m b/examples/HorizontalWithinVerticalScrolling/Sample/ViewController.m index b8297f5fe6..416000b92f 100644 --- a/examples/HorizontalWithinVerticalScrolling/Sample/ViewController.m +++ b/examples/HorizontalWithinVerticalScrolling/Sample/ViewController.m @@ -62,11 +62,6 @@ _tableView.frame = self.view.bounds; } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - #pragma mark - #pragma mark ASTableView. diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 2f2c7136ba..9ec66fa418 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -101,11 +101,6 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell _tableView.frame = self.view.bounds; } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - - (void)toggleEditingMode { [_tableView setEditing:!_tableView.editing animated:YES]; diff --git a/examples/Multiplex/Sample/ViewController.m b/examples/Multiplex/Sample/ViewController.m index 2b5471d6f5..08a62ede74 100644 --- a/examples/Multiplex/Sample/ViewController.m +++ b/examples/Multiplex/Sample/ViewController.m @@ -39,9 +39,4 @@ [super viewWillAppear:animated]; } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - @end diff --git a/examples/README.md b/examples/README.md index 358c9162ff..87c32c6375 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,6 +5,178 @@ Run `pod install` in each sample project directory to set up their dependencies. +## Example Catalog + +### ASCollectionView [ObjC] + +![ASCollectionView Example App Screenshot](./Screenshots/ASCollectionView.png?raw=true) + +Featuring: +- ASCollectionView with header/footer supplementary node support +- ASCollectionView batch API +- ASDelegateProxy + +### ASTableViewStressTest [ObjC] + +![ASTableViewStressTest Example App Screenshot](./Screenshots/ASTableViewStressTest.png?raw=true) + +### ASViewController [ObjC] + +![ASViewController Example App Screenshot](./Screenshots/ASViewController.png?raw=true) + +Featuring: +- ASViewController +- ASTableView +- ASMultiplexImageNode +- ASLayoutSpec + +### BackgroundPropertySetting [Swift] + +![BackgroundPropertySetting Example App gif](./Screenshots/BackgroundPropertySetting.gif?raw=true) + +Featuring: +- ASDK Swift compatibility +- ASViewController +- ASCollectionView +- thread affinity +- ASLayoutSpec + +### CarthageBuildTest +### CatDealsCollectionView [ObjC] + +![CatDealsCollectionView Example App Screenshot](./Screenshots/CatDealsCollectionView.png?raw=true) + +Featuring: +- ASCollectionView +- ASRangeTuningParameters +- Placeholder Images +- ASLayoutSpec + +### CollectionViewWithViewControllerCells [ObjC] + +![CollectionViewWithViewControllerCells Example App Screenshot](./Screenshots/CollectionViewWithViewControllerCells.png?raw=true) + +Featuring: +- custom collection view layout +- ASLayoutSpec +- ASMultiplexImageNode + +### CustomCollectionView [ObjC] + +![CustomCollectionView Example App gif](./Screenshots/CustomCollectionView.gif?raw=true) + +Featuring: +- custom collection view layout +- ASCollectionView with sections + +### EditableText [ObjC] + +![EditableText Example App Screenshot](./Screenshots/EditableText.png?raw=true) + +Featuring: +- ASEditableTextNode + +### HorizontalwithinVerticalScrolling [ObjC] + +![HorizontalwithinVerticalScrolling Example App gif](./Screenshots/HorizontalwithinVerticalScrolling.gif?raw=true) + +Featuring: +- UIViewController with ASTableView +- ASCollectionView +- ASCellNode + +### Kittens [ObjC] + +![Kittens Example App Screenshot](./Screenshots/Kittens.png?raw=true) + +Featuring: +- UIViewController with ASTableView +- ASCellNodes with ASNetworkImageNode and ASTextNode + +### Multiplex [ObjC] + +![Multiplex Example App gif](./Screenshots/Multiplex.gif?raw=true) + +Featuring: +- ASMultiplexImageNode (with artificial delay inserted) +- ASLayoutSpec + +### PagerNode [ObjC] + +Featuring: +- ASPagerNode + +### Placeholders [ObjC] + +Featuring: +- ASDisplayNodes now have an overidable method -placeholderImage that lets you provide a custom UIImage to display while a node is displaying asyncronously. The default implementation of this method returns nil and thus does nothing. A provided example project also demonstrates using the placeholder API. + +### SocialAppLayout [ObjC] + +![SocialAppLayout Example App Screenshot](./Screenshots/SocialAppLayout.png?raw=true) + +Featuring: +- ASLayoutSpec +- UIViewController with ASTableView + +### Swift [Swift] + +![Swift Example App Screenshot](./Screenshots/Swift.png?raw=true) + +Featuring: +- ASViewController with ASTableNode + +### SynchronousConcurrency [ObjC] + +![SynchronousConcurrency Example App Screenshot](./Screenshots/SynchronousConcurrency.png?raw=true) + +Implementation of Synchronous Concurrency features for AsyncDisplayKit 2.0 + +This provides internal features on _ASAsyncTransaction and ASDisplayNode to facilitate +implementing public API that allows clients to choose if they would prefer to block +on the completion of unfinished rendering, rather than allow a placeholder state to +become visible. + +The internal features are: +-[_ASAsyncTransaction waitUntilComplete] +-[ASDisplayNode recursivelyEnsureDisplay] + +Also provided are two such implementations: +-[ASCellNode setNeverShowPlaceholders:], which integrates with both Tables and Collections +-[ASViewController setNeverShowPlaceholders:], which should work with Nav and Tab controllers. + +Lastly, on ASDisplayNode, a new property .shouldBypassEnsureDisplay allows individual node types +to exempt themselves from blocking the main thread on their display. + +By implementing the feature at the ASCellNode level rather than ASTableView & ASCollectionView, +developers can retain fine-grained control on display characteristics. For example, certain +cell types may be appropriate to display to the user with placeholders, whereas others may not. + +### SynchronousKittens [ObjC] + +### VerticalWithinHorizontalScrolling [ObjC] + +![VerticalWithinHorizontalScrolling Example App Screenshot](./Screenshots/VerticalWithinHorizontalScrolling.png?raw=true) + +Features: +- UIViewController containing ASPagerNode containing ASTableNodes + +### Videos [ObjC] + +![VideoTableView Example App gif](./Screenshots/Videos.gif?raw=true) + +Featuring: +- ASVideoNode + +### VideoTableView [ObjC] + +![VideoTableView Example App Screenshot](./Screenshots/VideoTableView.png?raw=true) + +Featuring: +- ASVideoNode +- ASTableView +- ASCellNode + ## License This file provided by Facebook is for non-commercial testing and evaluation diff --git a/examples/Screenshots/ASCollectionView.png b/examples/Screenshots/ASCollectionView.png new file mode 100644 index 0000000000..3aaff368e5 Binary files /dev/null and b/examples/Screenshots/ASCollectionView.png differ diff --git a/examples/Screenshots/ASTableViewStressTest.png b/examples/Screenshots/ASTableViewStressTest.png new file mode 100644 index 0000000000..cd7aae2d9d Binary files /dev/null and b/examples/Screenshots/ASTableViewStressTest.png differ diff --git a/examples/Screenshots/ASViewController.png b/examples/Screenshots/ASViewController.png new file mode 100644 index 0000000000..545f3c8d63 Binary files /dev/null and b/examples/Screenshots/ASViewController.png differ diff --git a/examples/Screenshots/BackgroundPropertySetting.gif b/examples/Screenshots/BackgroundPropertySetting.gif new file mode 100644 index 0000000000..e2655ef235 Binary files /dev/null and b/examples/Screenshots/BackgroundPropertySetting.gif differ diff --git a/examples/Screenshots/CatDealsCollectionView.png b/examples/Screenshots/CatDealsCollectionView.png new file mode 100644 index 0000000000..72f9179e94 Binary files /dev/null and b/examples/Screenshots/CatDealsCollectionView.png differ diff --git a/examples/Screenshots/CollectionViewWithViewControllerCells.png b/examples/Screenshots/CollectionViewWithViewControllerCells.png new file mode 100644 index 0000000000..078b7b945f Binary files /dev/null and b/examples/Screenshots/CollectionViewWithViewControllerCells.png differ diff --git a/examples/Screenshots/CustomCollectionView.gif b/examples/Screenshots/CustomCollectionView.gif new file mode 100644 index 0000000000..8d8a2aed1e Binary files /dev/null and b/examples/Screenshots/CustomCollectionView.gif differ diff --git a/examples/Screenshots/EditableText.png b/examples/Screenshots/EditableText.png new file mode 100644 index 0000000000..1aa8d6db9b Binary files /dev/null and b/examples/Screenshots/EditableText.png differ diff --git a/examples/Screenshots/HorizontalwithinVerticalScrolling.gif b/examples/Screenshots/HorizontalwithinVerticalScrolling.gif new file mode 100644 index 0000000000..fe722a308d Binary files /dev/null and b/examples/Screenshots/HorizontalwithinVerticalScrolling.gif differ diff --git a/examples/Screenshots/Kittens.png b/examples/Screenshots/Kittens.png new file mode 100644 index 0000000000..91d9bf36fa Binary files /dev/null and b/examples/Screenshots/Kittens.png differ diff --git a/examples/Screenshots/Multiplex.gif b/examples/Screenshots/Multiplex.gif new file mode 100644 index 0000000000..fcb98cad62 Binary files /dev/null and b/examples/Screenshots/Multiplex.gif differ diff --git a/examples/Screenshots/SocialAppLayout.png b/examples/Screenshots/SocialAppLayout.png new file mode 100644 index 0000000000..33d6672102 Binary files /dev/null and b/examples/Screenshots/SocialAppLayout.png differ diff --git a/examples/Screenshots/Swift.png b/examples/Screenshots/Swift.png new file mode 100644 index 0000000000..b099a17229 Binary files /dev/null and b/examples/Screenshots/Swift.png differ diff --git a/examples/Screenshots/SynchronousConcurrency.png b/examples/Screenshots/SynchronousConcurrency.png new file mode 100644 index 0000000000..e0eac3788e Binary files /dev/null and b/examples/Screenshots/SynchronousConcurrency.png differ diff --git a/examples/Screenshots/VerticalWithinHorizontalScrolling.gif b/examples/Screenshots/VerticalWithinHorizontalScrolling.gif new file mode 100644 index 0000000000..c50474b19b Binary files /dev/null and b/examples/Screenshots/VerticalWithinHorizontalScrolling.gif differ diff --git a/examples/Screenshots/VideoTableView.png b/examples/Screenshots/VideoTableView.png new file mode 100644 index 0000000000..ddeefdc773 Binary files /dev/null and b/examples/Screenshots/VideoTableView.png differ diff --git a/examples/Screenshots/Videos.gif b/examples/Screenshots/Videos.gif new file mode 100644 index 0000000000..5e5c5f2ca9 Binary files /dev/null and b/examples/Screenshots/Videos.gif differ diff --git a/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj b/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj index 6b5d2a4bbf..b14014b7a0 100644 --- a/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj +++ b/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj @@ -1,466 +1,1311 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1DAE4C3A5DA2C2B081CD640F /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7577801AA7321797C5FBB9A1 /* libPods.a */; }; - 3EEA4EE91BECC4A1008A7F35 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4EE81BECC4A1008A7F35 /* main.m */; }; - 3EEA4EEC1BECC4A1008A7F35 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4EEB1BECC4A1008A7F35 /* AppDelegate.m */; }; - 3EEA4EEF1BECC4A1008A7F35 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4EEE1BECC4A1008A7F35 /* ViewController.m */; }; - 3EEA4EF41BECC4A1008A7F35 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4EF31BECC4A1008A7F35 /* Assets.xcassets */; }; - 3EEA4F011BECC4E8008A7F35 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4EFE1BECC4E8008A7F35 /* Default-568h@2x.png */; }; - 3EEA4F021BECC4E8008A7F35 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4EFF1BECC4E8008A7F35 /* Default-667h@2x.png */; }; - 3EEA4F031BECC4E8008A7F35 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F001BECC4E8008A7F35 /* Default-736h@3x.png */; }; - 3EEA4F061BECC6C9008A7F35 /* Post.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F051BECC6C9008A7F35 /* Post.m */; }; - 3EEA4F091BECC855008A7F35 /* PostNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F081BECC855008A7F35 /* PostNode.m */; }; - 3EEA4F0C1BECCA0A008A7F35 /* TextStyles.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F0B1BECCA0A008A7F35 /* TextStyles.m */; }; - 3EEA4F141BECDCD6008A7F35 /* icon_android.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F0E1BECDCD6008A7F35 /* icon_android.png */; }; - 3EEA4F151BECDCD6008A7F35 /* icon_android@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F0F1BECDCD6008A7F35 /* icon_android@2x.png */; }; - 3EEA4F161BECDCD6008A7F35 /* icon_android@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F101BECDCD6008A7F35 /* icon_android@3x.png */; }; - 3EEA4F171BECDCD6008A7F35 /* icon_ios.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F111BECDCD6008A7F35 /* icon_ios.png */; }; - 3EEA4F181BECDCD6008A7F35 /* icon_ios@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F121BECDCD6008A7F35 /* icon_ios@2x.png */; }; - 3EEA4F191BECDCD6008A7F35 /* icon_ios@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F131BECDCD6008A7F35 /* icon_ios@3x.png */; }; - 3EEA4F1D1BECE358008A7F35 /* LikesNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F1C1BECE358008A7F35 /* LikesNode.m */; }; - 3EEA4F2A1BECE440008A7F35 /* icon_liked.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F1E1BECE440008A7F35 /* icon_liked.png */; }; - 3EEA4F2B1BECE440008A7F35 /* icon_liked@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F1F1BECE440008A7F35 /* icon_liked@2x.png */; }; - 3EEA4F2C1BECE440008A7F35 /* icon_liked@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F201BECE440008A7F35 /* icon_liked@3x.png */; }; - 3EEA4F301BECE440008A7F35 /* icon_like@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F241BECE440008A7F35 /* icon_like@3x.png */; }; - 3EEA4F311BECE440008A7F35 /* icon_like@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F251BECE440008A7F35 /* icon_like@2x.png */; }; - 3EEA4F321BECE440008A7F35 /* icon_like.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F261BECE440008A7F35 /* icon_like.png */; }; - 3EEA4F331BECE440008A7F35 /* icon_comment@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F271BECE440008A7F35 /* icon_comment@3x.png */; }; - 3EEA4F341BECE440008A7F35 /* icon_comment@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F281BECE440008A7F35 /* icon_comment@2x.png */; }; - 3EEA4F351BECE440008A7F35 /* icon_comment.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F291BECE440008A7F35 /* icon_comment.png */; }; - 3EEA4F381BECE775008A7F35 /* CommentsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F371BECE775008A7F35 /* CommentsNode.m */; }; - 3EEA4F3C1BECE99F008A7F35 /* icon_more.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F391BECE99F008A7F35 /* icon_more.png */; }; - 3EEA4F3D1BECE99F008A7F35 /* icon_more@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F3A1BECE99F008A7F35 /* icon_more@2x.png */; }; - 3EEA4F3E1BECE99F008A7F35 /* icon_more@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F3B1BECE99F008A7F35 /* icon_more@3x.png */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 3EEA4EE41BECC4A1008A7F35 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 3EEA4EE81BECC4A1008A7F35 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 3EEA4EEA1BECC4A1008A7F35 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 3EEA4EEB1BECC4A1008A7F35 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 3EEA4EED1BECC4A1008A7F35 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - 3EEA4EEE1BECC4A1008A7F35 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - 3EEA4EF31BECC4A1008A7F35 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 3EEA4EF81BECC4A1008A7F35 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3EEA4EFE1BECC4E8008A7F35 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; - 3EEA4EFF1BECC4E8008A7F35 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = ""; }; - 3EEA4F001BECC4E8008A7F35 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = ""; }; - 3EEA4F041BECC6C9008A7F35 /* Post.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Post.h; sourceTree = ""; }; - 3EEA4F051BECC6C9008A7F35 /* Post.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Post.m; sourceTree = ""; }; - 3EEA4F071BECC855008A7F35 /* PostNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PostNode.h; sourceTree = ""; }; - 3EEA4F081BECC855008A7F35 /* PostNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostNode.m; sourceTree = ""; }; - 3EEA4F0A1BECCA0A008A7F35 /* TextStyles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextStyles.h; sourceTree = ""; }; - 3EEA4F0B1BECCA0A008A7F35 /* TextStyles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TextStyles.m; sourceTree = ""; }; - 3EEA4F0E1BECDCD6008A7F35 /* icon_android.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_android.png; sourceTree = ""; }; - 3EEA4F0F1BECDCD6008A7F35 /* icon_android@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_android@2x.png"; sourceTree = ""; }; - 3EEA4F101BECDCD6008A7F35 /* icon_android@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_android@3x.png"; sourceTree = ""; }; - 3EEA4F111BECDCD6008A7F35 /* icon_ios.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_ios.png; sourceTree = ""; }; - 3EEA4F121BECDCD6008A7F35 /* icon_ios@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_ios@2x.png"; sourceTree = ""; }; - 3EEA4F131BECDCD6008A7F35 /* icon_ios@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_ios@3x.png"; sourceTree = ""; }; - 3EEA4F1B1BECE358008A7F35 /* LikesNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LikesNode.h; sourceTree = ""; }; - 3EEA4F1C1BECE358008A7F35 /* LikesNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LikesNode.m; sourceTree = ""; }; - 3EEA4F1E1BECE440008A7F35 /* icon_liked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_liked.png; sourceTree = ""; }; - 3EEA4F1F1BECE440008A7F35 /* icon_liked@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_liked@2x.png"; sourceTree = ""; }; - 3EEA4F201BECE440008A7F35 /* icon_liked@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_liked@3x.png"; sourceTree = ""; }; - 3EEA4F241BECE440008A7F35 /* icon_like@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_like@3x.png"; sourceTree = ""; }; - 3EEA4F251BECE440008A7F35 /* icon_like@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_like@2x.png"; sourceTree = ""; }; - 3EEA4F261BECE440008A7F35 /* icon_like.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_like.png; sourceTree = ""; }; - 3EEA4F271BECE440008A7F35 /* icon_comment@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_comment@3x.png"; sourceTree = ""; }; - 3EEA4F281BECE440008A7F35 /* icon_comment@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_comment@2x.png"; sourceTree = ""; }; - 3EEA4F291BECE440008A7F35 /* icon_comment.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_comment.png; sourceTree = ""; }; - 3EEA4F361BECE775008A7F35 /* CommentsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentsNode.h; sourceTree = ""; }; - 3EEA4F371BECE775008A7F35 /* CommentsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentsNode.m; sourceTree = ""; }; - 3EEA4F391BECE99F008A7F35 /* icon_more.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_more.png; sourceTree = ""; }; - 3EEA4F3A1BECE99F008A7F35 /* icon_more@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_more@2x.png"; sourceTree = ""; }; - 3EEA4F3B1BECE99F008A7F35 /* icon_more@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_more@3x.png"; sourceTree = ""; }; - 7577801AA7321797C5FBB9A1 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 839964CA580D0ED921A6FCB1 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; - E38AD915EE10CFC641F39E5C /* 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 */ - 3EEA4EE11BECC4A1008A7F35 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1DAE4C3A5DA2C2B081CD640F /* libPods.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 3EEA4EDB1BECC4A1008A7F35 = { - isa = PBXGroup; - children = ( - 3EEA4EE61BECC4A1008A7F35 /* Sample */, - 3EEA4EE51BECC4A1008A7F35 /* Products */, - 842ADAFE88475D19B24183AC /* Pods */, - EED34FA6D8171DF44757C852 /* Frameworks */, - ); - sourceTree = ""; - }; - 3EEA4EE51BECC4A1008A7F35 /* Products */ = { - isa = PBXGroup; - children = ( - 3EEA4EE41BECC4A1008A7F35 /* Sample.app */, - ); - name = Products; - sourceTree = ""; - }; - 3EEA4EE61BECC4A1008A7F35 /* Sample */ = { - isa = PBXGroup; - children = ( - 3EEA4F0D1BECDCA6008A7F35 /* Images */, - 3EEA4EEA1BECC4A1008A7F35 /* AppDelegate.h */, - 3EEA4EEB1BECC4A1008A7F35 /* AppDelegate.m */, - 3EEA4EED1BECC4A1008A7F35 /* ViewController.h */, - 3EEA4EEE1BECC4A1008A7F35 /* ViewController.m */, - 3EEA4EF31BECC4A1008A7F35 /* Assets.xcassets */, - 3EEA4EF81BECC4A1008A7F35 /* Info.plist */, - 3EEA4EE71BECC4A1008A7F35 /* Supporting Files */, - 3EEA4F041BECC6C9008A7F35 /* Post.h */, - 3EEA4F051BECC6C9008A7F35 /* Post.m */, - 3EEA4F071BECC855008A7F35 /* PostNode.h */, - 3EEA4F081BECC855008A7F35 /* PostNode.m */, - 3EEA4F0A1BECCA0A008A7F35 /* TextStyles.h */, - 3EEA4F0B1BECCA0A008A7F35 /* TextStyles.m */, - 3EEA4F1B1BECE358008A7F35 /* LikesNode.h */, - 3EEA4F1C1BECE358008A7F35 /* LikesNode.m */, - 3EEA4F361BECE775008A7F35 /* CommentsNode.h */, - 3EEA4F371BECE775008A7F35 /* CommentsNode.m */, - ); - path = Sample; - sourceTree = ""; - }; - 3EEA4EE71BECC4A1008A7F35 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 3EEA4EFE1BECC4E8008A7F35 /* Default-568h@2x.png */, - 3EEA4EFF1BECC4E8008A7F35 /* Default-667h@2x.png */, - 3EEA4F001BECC4E8008A7F35 /* Default-736h@3x.png */, - 3EEA4EE81BECC4A1008A7F35 /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 3EEA4F0D1BECDCA6008A7F35 /* Images */ = { - isa = PBXGroup; - children = ( - 3EEA4F391BECE99F008A7F35 /* icon_more.png */, - 3EEA4F3A1BECE99F008A7F35 /* icon_more@2x.png */, - 3EEA4F3B1BECE99F008A7F35 /* icon_more@3x.png */, - 3EEA4F1E1BECE440008A7F35 /* icon_liked.png */, - 3EEA4F1F1BECE440008A7F35 /* icon_liked@2x.png */, - 3EEA4F201BECE440008A7F35 /* icon_liked@3x.png */, - 3EEA4F241BECE440008A7F35 /* icon_like@3x.png */, - 3EEA4F251BECE440008A7F35 /* icon_like@2x.png */, - 3EEA4F261BECE440008A7F35 /* icon_like.png */, - 3EEA4F271BECE440008A7F35 /* icon_comment@3x.png */, - 3EEA4F281BECE440008A7F35 /* icon_comment@2x.png */, - 3EEA4F291BECE440008A7F35 /* icon_comment.png */, - 3EEA4F0E1BECDCD6008A7F35 /* icon_android.png */, - 3EEA4F0F1BECDCD6008A7F35 /* icon_android@2x.png */, - 3EEA4F101BECDCD6008A7F35 /* icon_android@3x.png */, - 3EEA4F111BECDCD6008A7F35 /* icon_ios.png */, - 3EEA4F121BECDCD6008A7F35 /* icon_ios@2x.png */, - 3EEA4F131BECDCD6008A7F35 /* icon_ios@3x.png */, - ); - name = Images; - sourceTree = ""; - }; - 842ADAFE88475D19B24183AC /* Pods */ = { - isa = PBXGroup; - children = ( - E38AD915EE10CFC641F39E5C /* Pods.debug.xcconfig */, - 839964CA580D0ED921A6FCB1 /* Pods.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; - EED34FA6D8171DF44757C852 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 7577801AA7321797C5FBB9A1 /* libPods.a */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 3EEA4EE31BECC4A1008A7F35 /* Sample */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3EEA4EFB1BECC4A1008A7F35 /* Build configuration list for PBXNativeTarget "Sample" */; - buildPhases = ( - B5BD9E5609B2CB179EEE0CF4 /* Check Pods Manifest.lock */, - 3EEA4EE01BECC4A1008A7F35 /* Sources */, - 3EEA4EE11BECC4A1008A7F35 /* Frameworks */, - 3EEA4EE21BECC4A1008A7F35 /* Resources */, - 21F2C1D9B53F9468EAF1653F /* Copy Pods Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Sample; - productName = Sample; - productReference = 3EEA4EE41BECC4A1008A7F35 /* Sample.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 3EEA4EDC1BECC4A1008A7F35 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0710; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 3EEA4EE31BECC4A1008A7F35 = { - CreatedOnToolsVersion = 7.1; - }; - }; - }; - buildConfigurationList = 3EEA4EDF1BECC4A1008A7F35 /* Build configuration list for PBXProject "Sample" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 3EEA4EDB1BECC4A1008A7F35; - productRefGroup = 3EEA4EE51BECC4A1008A7F35 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 3EEA4EE31BECC4A1008A7F35 /* Sample */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 3EEA4EE21BECC4A1008A7F35 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3EEA4F2A1BECE440008A7F35 /* icon_liked.png in Resources */, - 3EEA4F181BECDCD6008A7F35 /* icon_ios@2x.png in Resources */, - 3EEA4F311BECE440008A7F35 /* icon_like@2x.png in Resources */, - 3EEA4F3D1BECE99F008A7F35 /* icon_more@2x.png in Resources */, - 3EEA4F141BECDCD6008A7F35 /* icon_android.png in Resources */, - 3EEA4F3E1BECE99F008A7F35 /* icon_more@3x.png in Resources */, - 3EEA4F2B1BECE440008A7F35 /* icon_liked@2x.png in Resources */, - 3EEA4F351BECE440008A7F35 /* icon_comment.png in Resources */, - 3EEA4EF41BECC4A1008A7F35 /* Assets.xcassets in Resources */, - 3EEA4F171BECDCD6008A7F35 /* icon_ios.png in Resources */, - 3EEA4F021BECC4E8008A7F35 /* Default-667h@2x.png in Resources */, - 3EEA4F161BECDCD6008A7F35 /* icon_android@3x.png in Resources */, - 3EEA4F191BECDCD6008A7F35 /* icon_ios@3x.png in Resources */, - 3EEA4F331BECE440008A7F35 /* icon_comment@3x.png in Resources */, - 3EEA4F341BECE440008A7F35 /* icon_comment@2x.png in Resources */, - 3EEA4F321BECE440008A7F35 /* icon_like.png in Resources */, - 3EEA4F151BECDCD6008A7F35 /* icon_android@2x.png in Resources */, - 3EEA4F301BECE440008A7F35 /* icon_like@3x.png in Resources */, - 3EEA4F2C1BECE440008A7F35 /* icon_liked@3x.png in Resources */, - 3EEA4F011BECC4E8008A7F35 /* Default-568h@2x.png in Resources */, - 3EEA4F031BECC4E8008A7F35 /* Default-736h@3x.png in Resources */, - 3EEA4F3C1BECE99F008A7F35 /* icon_more.png in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 21F2C1D9B53F9468EAF1653F /* 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; - }; - B5BD9E5609B2CB179EEE0CF4 /* 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; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 3EEA4EE01BECC4A1008A7F35 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3EEA4EEF1BECC4A1008A7F35 /* ViewController.m in Sources */, - 3EEA4EEC1BECC4A1008A7F35 /* AppDelegate.m in Sources */, - 3EEA4EE91BECC4A1008A7F35 /* main.m in Sources */, - 3EEA4F061BECC6C9008A7F35 /* Post.m in Sources */, - 3EEA4F0C1BECCA0A008A7F35 /* TextStyles.m in Sources */, - 3EEA4F381BECE775008A7F35 /* CommentsNode.m in Sources */, - 3EEA4F091BECC855008A7F35 /* PostNode.m in Sources */, - 3EEA4F1D1BECE358008A7F35 /* LikesNode.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 3EEA4EF91BECC4A1008A7F35 /* 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; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - 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 = 9.1; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 3EEA4EFA1BECC4A1008A7F35 /* 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 = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - 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 = 9.1; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 3EEA4EFC1BECC4A1008A7F35 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = E38AD915EE10CFC641F39E5C /* 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_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; - PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 3EEA4EFD1BECC4A1008A7F35 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 839964CA580D0ED921A6FCB1 /* 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_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; - PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 3EEA4EDF1BECC4A1008A7F35 /* Build configuration list for PBXProject "Sample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3EEA4EF91BECC4A1008A7F35 /* Debug */, - 3EEA4EFA1BECC4A1008A7F35 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 3EEA4EFB1BECC4A1008A7F35 /* Build configuration list for PBXNativeTarget "Sample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3EEA4EFC1BECC4A1008A7F35 /* Debug */, - 3EEA4EFD1BECC4A1008A7F35 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 3EEA4EDC1BECC4A1008A7F35 /* Project object */; -} + + + + + archiveVersion + 1 + classes + + objectVersion + 46 + objects + + 1DAE4C3A5DA2C2B081CD640F + + fileRef + 7577801AA7321797C5FBB9A1 + isa + PBXBuildFile + + 21F2C1D9B53F9468EAF1653F + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + Copy Pods Resources + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + "${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh" + + showEnvVarsInLog + 0 + + 3EEA4EDB1BECC4A1008A7F35 + + children + + 3EEA4EE61BECC4A1008A7F35 + 3EEA4EE51BECC4A1008A7F35 + 842ADAFE88475D19B24183AC + EED34FA6D8171DF44757C852 + + isa + PBXGroup + sourceTree + <group> + + 3EEA4EDC1BECC4A1008A7F35 + + attributes + + LastUpgradeCheck + 0710 + ORGANIZATIONNAME + Facebook + TargetAttributes + + 3EEA4EE31BECC4A1008A7F35 + + CreatedOnToolsVersion + 7.1 + + + + buildConfigurationList + 3EEA4EDF1BECC4A1008A7F35 + compatibilityVersion + Xcode 3.2 + developmentRegion + English + hasScannedForEncodings + 0 + isa + PBXProject + knownRegions + + en + Base + + mainGroup + 3EEA4EDB1BECC4A1008A7F35 + productRefGroup + 3EEA4EE51BECC4A1008A7F35 + projectDirPath + + projectReferences + + projectRoot + + targets + + 3EEA4EE31BECC4A1008A7F35 + + + 3EEA4EDF1BECC4A1008A7F35 + + buildConfigurations + + 3EEA4EF91BECC4A1008A7F35 + 3EEA4EFA1BECC4A1008A7F35 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + 3EEA4EE01BECC4A1008A7F35 + + buildActionMask + 2147483647 + files + + 3EEA4EEF1BECC4A1008A7F35 + 3EEA4EEC1BECC4A1008A7F35 + 3EEA4EE91BECC4A1008A7F35 + 3EEA4F061BECC6C9008A7F35 + 3EEA4F0C1BECCA0A008A7F35 + 3EEA4F381BECE775008A7F35 + 3EEA4F091BECC855008A7F35 + 3EEA4F1D1BECE358008A7F35 + + isa + PBXSourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 3EEA4EE11BECC4A1008A7F35 + + buildActionMask + 2147483647 + files + + 1DAE4C3A5DA2C2B081CD640F + + isa + PBXFrameworksBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 3EEA4EE21BECC4A1008A7F35 + + buildActionMask + 2147483647 + files + + 3EEA4F2A1BECE440008A7F35 + 3EEA4F181BECDCD6008A7F35 + 3EEA4F311BECE440008A7F35 + 3EEA4F3D1BECE99F008A7F35 + 3EEA4F141BECDCD6008A7F35 + 3EEA4F3E1BECE99F008A7F35 + 3EEA4F2B1BECE440008A7F35 + 3EEA4F351BECE440008A7F35 + 3EEA4EF41BECC4A1008A7F35 + 3EEA4F171BECDCD6008A7F35 + 3EEA4F021BECC4E8008A7F35 + 3EEA4F161BECDCD6008A7F35 + 3EEA4F191BECDCD6008A7F35 + 3EEA4F331BECE440008A7F35 + 3EEA4F341BECE440008A7F35 + 3EEA4F321BECE440008A7F35 + 3EEA4F151BECDCD6008A7F35 + 3EEA4F301BECE440008A7F35 + 3EEA4F2C1BECE440008A7F35 + 3EEA4F011BECC4E8008A7F35 + 3EEA4F031BECC4E8008A7F35 + 3EEA4F3C1BECE99F008A7F35 + + isa + PBXResourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 3EEA4EE31BECC4A1008A7F35 + + buildConfigurationList + 3EEA4EFB1BECC4A1008A7F35 + buildPhases + + B5BD9E5609B2CB179EEE0CF4 + 3EEA4EE01BECC4A1008A7F35 + 3EEA4EE11BECC4A1008A7F35 + 3EEA4EE21BECC4A1008A7F35 + 21F2C1D9B53F9468EAF1653F + 852437589F1D53B9483A75DF + + buildRules + + dependencies + + isa + PBXNativeTarget + name + Sample + productName + Sample + productReference + 3EEA4EE41BECC4A1008A7F35 + productType + com.apple.product-type.application + + 3EEA4EE41BECC4A1008A7F35 + + explicitFileType + wrapper.application + includeInIndex + 0 + isa + PBXFileReference + path + Sample.app + sourceTree + BUILT_PRODUCTS_DIR + + 3EEA4EE51BECC4A1008A7F35 + + children + + 3EEA4EE41BECC4A1008A7F35 + + isa + PBXGroup + name + Products + sourceTree + <group> + + 3EEA4EE61BECC4A1008A7F35 + + children + + 3EEA4F0D1BECDCA6008A7F35 + 3EEA4EEA1BECC4A1008A7F35 + 3EEA4EEB1BECC4A1008A7F35 + 3EEA4EED1BECC4A1008A7F35 + 3EEA4EEE1BECC4A1008A7F35 + 3EEA4EF31BECC4A1008A7F35 + 3EEA4EF81BECC4A1008A7F35 + 3EEA4EE71BECC4A1008A7F35 + 3EEA4F041BECC6C9008A7F35 + 3EEA4F051BECC6C9008A7F35 + 3EEA4F071BECC855008A7F35 + 3EEA4F081BECC855008A7F35 + 3EEA4F0A1BECCA0A008A7F35 + 3EEA4F0B1BECCA0A008A7F35 + 3EEA4F1B1BECE358008A7F35 + 3EEA4F1C1BECE358008A7F35 + 3EEA4F361BECE775008A7F35 + 3EEA4F371BECE775008A7F35 + + isa + PBXGroup + path + Sample + sourceTree + <group> + + 3EEA4EE71BECC4A1008A7F35 + + children + + 3EEA4EFE1BECC4E8008A7F35 + 3EEA4EFF1BECC4E8008A7F35 + 3EEA4F001BECC4E8008A7F35 + 3EEA4EE81BECC4A1008A7F35 + + isa + PBXGroup + name + Supporting Files + sourceTree + <group> + + 3EEA4EE81BECC4A1008A7F35 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + main.m + sourceTree + <group> + + 3EEA4EE91BECC4A1008A7F35 + + fileRef + 3EEA4EE81BECC4A1008A7F35 + isa + PBXBuildFile + + 3EEA4EEA1BECC4A1008A7F35 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + AppDelegate.h + sourceTree + <group> + + 3EEA4EEB1BECC4A1008A7F35 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + AppDelegate.m + sourceTree + <group> + + 3EEA4EEC1BECC4A1008A7F35 + + fileRef + 3EEA4EEB1BECC4A1008A7F35 + isa + PBXBuildFile + + 3EEA4EED1BECC4A1008A7F35 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + ViewController.h + sourceTree + <group> + + 3EEA4EEE1BECC4A1008A7F35 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + ViewController.m + sourceTree + <group> + + 3EEA4EEF1BECC4A1008A7F35 + + fileRef + 3EEA4EEE1BECC4A1008A7F35 + isa + PBXBuildFile + + 3EEA4EF31BECC4A1008A7F35 + + isa + PBXFileReference + lastKnownFileType + folder.assetcatalog + path + Assets.xcassets + sourceTree + <group> + + 3EEA4EF41BECC4A1008A7F35 + + fileRef + 3EEA4EF31BECC4A1008A7F35 + isa + PBXBuildFile + + 3EEA4EF81BECC4A1008A7F35 + + isa + PBXFileReference + lastKnownFileType + text.plist.xml + path + Info.plist + sourceTree + <group> + + 3EEA4EF91BECC4A1008A7F35 + + 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 + DEBUG_INFORMATION_FORMAT + dwarf + ENABLE_STRICT_OBJC_MSGSEND + YES + ENABLE_TESTABILITY + YES + GCC_C_LANGUAGE_STANDARD + gnu99 + GCC_DYNAMIC_NO_PIC + NO + GCC_NO_COMMON_BLOCKS + YES + GCC_OPTIMIZATION_LEVEL + 0 + GCC_PREPROCESSOR_DEFINITIONS + + DEBUG=1 + $(inherited) + + 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 + 9.1 + MTL_ENABLE_DEBUG_INFO + YES + ONLY_ACTIVE_ARCH + YES + SDKROOT + iphoneos + + isa + XCBuildConfiguration + name + Debug + + 3EEA4EFA1BECC4A1008A7F35 + + 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 + DEBUG_INFORMATION_FORMAT + dwarf-with-dsym + ENABLE_NS_ASSERTIONS + NO + ENABLE_STRICT_OBJC_MSGSEND + YES + GCC_C_LANGUAGE_STANDARD + gnu99 + GCC_NO_COMMON_BLOCKS + YES + 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 + 9.1 + MTL_ENABLE_DEBUG_INFO + NO + SDKROOT + iphoneos + VALIDATE_PRODUCT + YES + + isa + XCBuildConfiguration + name + Release + + 3EEA4EFB1BECC4A1008A7F35 + + buildConfigurations + + 3EEA4EFC1BECC4A1008A7F35 + 3EEA4EFD1BECC4A1008A7F35 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + 3EEA4EFC1BECC4A1008A7F35 + + baseConfigurationReference + E38AD915EE10CFC641F39E5C + 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_BUNDLE_IDENTIFIER + com.facebook.AsyncDisplayKit.Sample + PRODUCT_NAME + $(TARGET_NAME) + TARGETED_DEVICE_FAMILY + 1,2 + + isa + XCBuildConfiguration + name + Debug + + 3EEA4EFD1BECC4A1008A7F35 + + baseConfigurationReference + 839964CA580D0ED921A6FCB1 + 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_BUNDLE_IDENTIFIER + com.facebook.AsyncDisplayKit.Sample + PRODUCT_NAME + $(TARGET_NAME) + TARGETED_DEVICE_FAMILY + 1,2 + + isa + XCBuildConfiguration + name + Release + + 3EEA4EFE1BECC4E8008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + Default-568h@2x.png + sourceTree + <group> + + 3EEA4EFF1BECC4E8008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + Default-667h@2x.png + sourceTree + <group> + + 3EEA4F001BECC4E8008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + Default-736h@3x.png + sourceTree + <group> + + 3EEA4F011BECC4E8008A7F35 + + fileRef + 3EEA4EFE1BECC4E8008A7F35 + isa + PBXBuildFile + + 3EEA4F021BECC4E8008A7F35 + + fileRef + 3EEA4EFF1BECC4E8008A7F35 + isa + PBXBuildFile + + 3EEA4F031BECC4E8008A7F35 + + fileRef + 3EEA4F001BECC4E8008A7F35 + isa + PBXBuildFile + + 3EEA4F041BECC6C9008A7F35 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + Post.h + sourceTree + <group> + + 3EEA4F051BECC6C9008A7F35 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + Post.m + sourceTree + <group> + + 3EEA4F061BECC6C9008A7F35 + + fileRef + 3EEA4F051BECC6C9008A7F35 + isa + PBXBuildFile + + 3EEA4F071BECC855008A7F35 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + PostNode.h + sourceTree + <group> + + 3EEA4F081BECC855008A7F35 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + PostNode.m + sourceTree + <group> + + 3EEA4F091BECC855008A7F35 + + fileRef + 3EEA4F081BECC855008A7F35 + isa + PBXBuildFile + + 3EEA4F0A1BECCA0A008A7F35 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + TextStyles.h + sourceTree + <group> + + 3EEA4F0B1BECCA0A008A7F35 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + TextStyles.m + sourceTree + <group> + + 3EEA4F0C1BECCA0A008A7F35 + + fileRef + 3EEA4F0B1BECCA0A008A7F35 + isa + PBXBuildFile + + 3EEA4F0D1BECDCA6008A7F35 + + children + + 3EEA4F391BECE99F008A7F35 + 3EEA4F3A1BECE99F008A7F35 + 3EEA4F3B1BECE99F008A7F35 + 3EEA4F1E1BECE440008A7F35 + 3EEA4F1F1BECE440008A7F35 + 3EEA4F201BECE440008A7F35 + 3EEA4F241BECE440008A7F35 + 3EEA4F251BECE440008A7F35 + 3EEA4F261BECE440008A7F35 + 3EEA4F271BECE440008A7F35 + 3EEA4F281BECE440008A7F35 + 3EEA4F291BECE440008A7F35 + 3EEA4F0E1BECDCD6008A7F35 + 3EEA4F0F1BECDCD6008A7F35 + 3EEA4F101BECDCD6008A7F35 + 3EEA4F111BECDCD6008A7F35 + 3EEA4F121BECDCD6008A7F35 + 3EEA4F131BECDCD6008A7F35 + + isa + PBXGroup + name + Images + sourceTree + <group> + + 3EEA4F0E1BECDCD6008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_android.png + sourceTree + <group> + + 3EEA4F0F1BECDCD6008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_android@2x.png + sourceTree + <group> + + 3EEA4F101BECDCD6008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_android@3x.png + sourceTree + <group> + + 3EEA4F111BECDCD6008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_ios.png + sourceTree + <group> + + 3EEA4F121BECDCD6008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_ios@2x.png + sourceTree + <group> + + 3EEA4F131BECDCD6008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_ios@3x.png + sourceTree + <group> + + 3EEA4F141BECDCD6008A7F35 + + fileRef + 3EEA4F0E1BECDCD6008A7F35 + isa + PBXBuildFile + + 3EEA4F151BECDCD6008A7F35 + + fileRef + 3EEA4F0F1BECDCD6008A7F35 + isa + PBXBuildFile + + 3EEA4F161BECDCD6008A7F35 + + fileRef + 3EEA4F101BECDCD6008A7F35 + isa + PBXBuildFile + + 3EEA4F171BECDCD6008A7F35 + + fileRef + 3EEA4F111BECDCD6008A7F35 + isa + PBXBuildFile + + 3EEA4F181BECDCD6008A7F35 + + fileRef + 3EEA4F121BECDCD6008A7F35 + isa + PBXBuildFile + + 3EEA4F191BECDCD6008A7F35 + + fileRef + 3EEA4F131BECDCD6008A7F35 + isa + PBXBuildFile + + 3EEA4F1B1BECE358008A7F35 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + LikesNode.h + sourceTree + <group> + + 3EEA4F1C1BECE358008A7F35 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + LikesNode.m + sourceTree + <group> + + 3EEA4F1D1BECE358008A7F35 + + fileRef + 3EEA4F1C1BECE358008A7F35 + isa + PBXBuildFile + + 3EEA4F1E1BECE440008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_liked.png + sourceTree + <group> + + 3EEA4F1F1BECE440008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_liked@2x.png + sourceTree + <group> + + 3EEA4F201BECE440008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_liked@3x.png + sourceTree + <group> + + 3EEA4F241BECE440008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_like@3x.png + sourceTree + <group> + + 3EEA4F251BECE440008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_like@2x.png + sourceTree + <group> + + 3EEA4F261BECE440008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_like.png + sourceTree + <group> + + 3EEA4F271BECE440008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_comment@3x.png + sourceTree + <group> + + 3EEA4F281BECE440008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_comment@2x.png + sourceTree + <group> + + 3EEA4F291BECE440008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_comment.png + sourceTree + <group> + + 3EEA4F2A1BECE440008A7F35 + + fileRef + 3EEA4F1E1BECE440008A7F35 + isa + PBXBuildFile + + 3EEA4F2B1BECE440008A7F35 + + fileRef + 3EEA4F1F1BECE440008A7F35 + isa + PBXBuildFile + + 3EEA4F2C1BECE440008A7F35 + + fileRef + 3EEA4F201BECE440008A7F35 + isa + PBXBuildFile + + 3EEA4F301BECE440008A7F35 + + fileRef + 3EEA4F241BECE440008A7F35 + isa + PBXBuildFile + + 3EEA4F311BECE440008A7F35 + + fileRef + 3EEA4F251BECE440008A7F35 + isa + PBXBuildFile + + 3EEA4F321BECE440008A7F35 + + fileRef + 3EEA4F261BECE440008A7F35 + isa + PBXBuildFile + + 3EEA4F331BECE440008A7F35 + + fileRef + 3EEA4F271BECE440008A7F35 + isa + PBXBuildFile + + 3EEA4F341BECE440008A7F35 + + fileRef + 3EEA4F281BECE440008A7F35 + isa + PBXBuildFile + + 3EEA4F351BECE440008A7F35 + + fileRef + 3EEA4F291BECE440008A7F35 + isa + PBXBuildFile + + 3EEA4F361BECE775008A7F35 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + CommentsNode.h + sourceTree + <group> + + 3EEA4F371BECE775008A7F35 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + CommentsNode.m + sourceTree + <group> + + 3EEA4F381BECE775008A7F35 + + fileRef + 3EEA4F371BECE775008A7F35 + isa + PBXBuildFile + + 3EEA4F391BECE99F008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_more.png + sourceTree + <group> + + 3EEA4F3A1BECE99F008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_more@2x.png + sourceTree + <group> + + 3EEA4F3B1BECE99F008A7F35 + + isa + PBXFileReference + lastKnownFileType + image.png + path + icon_more@3x.png + sourceTree + <group> + + 3EEA4F3C1BECE99F008A7F35 + + fileRef + 3EEA4F391BECE99F008A7F35 + isa + PBXBuildFile + + 3EEA4F3D1BECE99F008A7F35 + + fileRef + 3EEA4F3A1BECE99F008A7F35 + isa + PBXBuildFile + + 3EEA4F3E1BECE99F008A7F35 + + fileRef + 3EEA4F3B1BECE99F008A7F35 + isa + PBXBuildFile + + 7577801AA7321797C5FBB9A1 + + explicitFileType + archive.ar + includeInIndex + 0 + isa + PBXFileReference + path + libPods.a + sourceTree + BUILT_PRODUCTS_DIR + + 839964CA580D0ED921A6FCB1 + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + text.xcconfig + name + Pods.release.xcconfig + path + Pods/Target Support Files/Pods/Pods.release.xcconfig + sourceTree + <group> + + 842ADAFE88475D19B24183AC + + children + + E38AD915EE10CFC641F39E5C + 839964CA580D0ED921A6FCB1 + + isa + PBXGroup + name + Pods + sourceTree + <group> + + 852437589F1D53B9483A75DF + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + Embed Pods Frameworks + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + "${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh" + + showEnvVarsInLog + 0 + + B5BD9E5609B2CB179EEE0CF4 + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + Check Pods Manifest.lock + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null +if [[ $? != 0 ]] ; then + cat << EOM +error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. +EOM + exit 1 +fi + + showEnvVarsInLog + 0 + + E38AD915EE10CFC641F39E5C + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + text.xcconfig + name + Pods.debug.xcconfig + path + Pods/Target Support Files/Pods/Pods.debug.xcconfig + sourceTree + <group> + + EED34FA6D8171DF44757C852 + + children + + 7577801AA7321797C5FBB9A1 + + isa + PBXGroup + name + Frameworks + sourceTree + <group> + + + rootObject + 3EEA4EDC1BECC4A1008A7F35 + + diff --git a/examples/SocialAppLayout/Sample/AppDelegate.h b/examples/SocialAppLayout/Sample/AppDelegate.h index db4bc0a921..2aa29369b4 100644 --- a/examples/SocialAppLayout/Sample/AppDelegate.h +++ b/examples/SocialAppLayout/Sample/AppDelegate.h @@ -15,6 +15,4 @@ @property (strong, nonatomic) UIWindow *window; - @end - diff --git a/examples/SocialAppLayout/Sample/AppDelegate.m b/examples/SocialAppLayout/Sample/AppDelegate.m index 4b977d79e0..5bd8203ef3 100644 --- a/examples/SocialAppLayout/Sample/AppDelegate.m +++ b/examples/SocialAppLayout/Sample/AppDelegate.m @@ -10,23 +10,17 @@ */ #import "AppDelegate.h" - #import "ViewController.h" -@interface AppDelegate () - -@end - @implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]]; [self.window makeKeyAndVisible]; return YES; - } @end diff --git a/examples/SocialAppLayout/Sample/CommentsNode.h b/examples/SocialAppLayout/Sample/CommentsNode.h index 5deafb5743..254f6abe2b 100644 --- a/examples/SocialAppLayout/Sample/CommentsNode.h +++ b/examples/SocialAppLayout/Sample/CommentsNode.h @@ -11,14 +11,7 @@ #import -@interface CommentsNode : ASControlNode { - - ASImageNode *_iconNode; - ASTextNode *_countNode; - - NSInteger _comentsCount; - -} +@interface CommentsNode : ASControlNode - (instancetype)initWithCommentsCount:(NSInteger)comentsCount; diff --git a/examples/SocialAppLayout/Sample/CommentsNode.m b/examples/SocialAppLayout/Sample/CommentsNode.m index 97bcc37191..1e8bc94028 100644 --- a/examples/SocialAppLayout/Sample/CommentsNode.m +++ b/examples/SocialAppLayout/Sample/CommentsNode.m @@ -12,53 +12,47 @@ #import "CommentsNode.h" #import "TextStyles.h" +@interface CommentsNode () +@property (nonatomic, strong) ASImageNode *iconNode; +@property (nonatomic, strong) ASTextNode *countNode; +@property (nonatomic, assign) NSInteger commentsCount; +@end + @implementation CommentsNode -- (instancetype)initWithCommentsCount:(NSInteger)comentsCount { - +- (instancetype)initWithCommentsCount:(NSInteger)comentsCount +{ self = [super init]; - - if(self) { - - _comentsCount = comentsCount; + if (self) { + _commentsCount = comentsCount; _iconNode = [[ASImageNode alloc] init]; _iconNode.image = [UIImage imageNamed:@"icon_comment.png"]; [self addSubnode:_iconNode]; _countNode = [[ASTextNode alloc] init]; - if(_comentsCount > 0) { - - _countNode.attributedString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%ld", (long)_comentsCount] attributes:[TextStyles cellControlStyle]]; - + if (_commentsCount > 0) { + _countNode.attributedString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%zd", _commentsCount] attributes:[TextStyles cellControlStyle]]; } - [self addSubnode:_countNode]; // make it tappable easily self.hitTestSlop = UIEdgeInsetsMake(-10, -10, -10, -10); - } return self; } -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { - - ASStackLayoutSpec *mainStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:6.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsCenter children:@[_iconNode, _countNode]]; +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASStackLayoutSpec *mainStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:6.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsCenter children:@[self.iconNode, self.countNode]]; // set sizeRange to make width fixed to 60 - mainStack.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake( - ASRelativeDimensionMakeWithPoints(60.0), - ASRelativeDimensionMakeWithPoints(0.0) - ), ASRelativeSizeMake( - ASRelativeDimensionMakeWithPoints(60.0), - ASRelativeDimensionMakeWithPoints(40.0) - )); + ASRelativeSize min = ASRelativeSizeMake(ASRelativeDimensionMakeWithPoints(60.0), ASRelativeDimensionMakeWithPoints(0.0)); + ASRelativeSize max = ASRelativeSizeMake(ASRelativeDimensionMakeWithPoints(60.0), ASRelativeDimensionMakeWithPoints(40.0)); + mainStack.sizeRange = ASRelativeSizeRangeMake(min,max); return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[mainStack]]; - } - @end diff --git a/examples/SocialAppLayout/Sample/LikesNode.h b/examples/SocialAppLayout/Sample/LikesNode.h index eb7d778a6c..d294459bc5 100644 --- a/examples/SocialAppLayout/Sample/LikesNode.h +++ b/examples/SocialAppLayout/Sample/LikesNode.h @@ -11,18 +11,8 @@ #import -@interface LikesNode : ASControlNode { - - ASImageNode *_iconNode; - ASTextNode *_countNode; - - NSInteger _likesCount; - BOOL _liked; - -} +@interface LikesNode : ASControlNode - (instancetype)initWithLikesCount:(NSInteger)likesCount; -+ (BOOL) getYesOrNo; - @end diff --git a/examples/SocialAppLayout/Sample/LikesNode.m b/examples/SocialAppLayout/Sample/LikesNode.m index 824fa43a6a..0a6eb9bc6a 100644 --- a/examples/SocialAppLayout/Sample/LikesNode.m +++ b/examples/SocialAppLayout/Sample/LikesNode.m @@ -12,14 +12,19 @@ #import "LikesNode.h" #import "TextStyles.h" -@implementation LikesNode +@interface LikesNode () +@property (nonatomic, strong) ASImageNode *iconNode; +@property (nonatomic, strong) ASTextNode *countNode; +@property (nonatomic, assign) NSInteger likesCount; +@property (nonatomic, assign) BOOL liked; +@end -- (instancetype)initWithLikesCount:(NSInteger)likesCount { - +@implementation LikesNode + +- (instancetype)initWithLikesCount:(NSInteger)likesCount +{ self = [super init]; - - if(self) { - + if (self) { _likesCount = likesCount; _liked = (_likesCount > 0) ? [LikesNode getYesOrNo] : NO; @@ -28,16 +33,12 @@ [self addSubnode:_iconNode]; _countNode = [[ASTextNode alloc] init]; - if(_likesCount > 0) { + if (_likesCount > 0) { - if(_liked) { - _countNode.attributedString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%ld", (long)_likesCount] attributes:[TextStyles cellControlColoredStyle]]; - }else { - _countNode.attributedString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%ld", (long)_likesCount] attributes:[TextStyles cellControlStyle]]; - } + NSDictionary *attributes = _liked ? [TextStyles cellControlColoredStyle] : [TextStyles cellControlStyle]; + _countNode.attributedString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%ld", (long)_likesCount] attributes:attributes]; } - [self addSubnode:_countNode]; // make it tappable easily @@ -48,28 +49,24 @@ } -+ (BOOL) getYesOrNo ++ (BOOL)getYesOrNo { int tmp = (arc4random() % 30)+1; - if(tmp % 5 == 0) + if (tmp % 5 == 0) { return YES; + } return NO; } -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { - +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ ASStackLayoutSpec *mainStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:6.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsCenter children:@[_iconNode, _countNode]]; // set sizeRange to make width fixed to 60 - mainStack.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake( - ASRelativeDimensionMakeWithPoints(60.0), - ASRelativeDimensionMakeWithPoints(0.0) - ), ASRelativeSizeMake( - ASRelativeDimensionMakeWithPoints(60.0), - ASRelativeDimensionMakeWithPoints(40.0) - )); + ASRelativeSize min = ASRelativeSizeMake(ASRelativeDimensionMakeWithPoints(60.0), ASRelativeDimensionMakeWithPoints(0.0)); + ASRelativeSize max = ASRelativeSizeMake(ASRelativeDimensionMakeWithPoints(60.0), ASRelativeDimensionMakeWithPoints(40.0)); + mainStack.sizeRange = ASRelativeSizeRangeMake(min, max); return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[mainStack]]; - } @end diff --git a/examples/SocialAppLayout/Sample/Post.h b/examples/SocialAppLayout/Sample/Post.h index d1e7f7c0aa..58be8bad74 100644 --- a/examples/SocialAppLayout/Sample/Post.h +++ b/examples/SocialAppLayout/Sample/Post.h @@ -13,12 +13,12 @@ @interface Post : NSObject -@property (nonatomic, strong) NSString *username; -@property (nonatomic, strong) NSString *name; -@property (nonatomic, strong) NSString *photo; -@property (nonatomic, strong) NSString *post; -@property (nonatomic, strong) NSString *time; -@property (nonatomic, strong) NSString *media; +@property (nonatomic, copy) NSString *username; +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *photo; +@property (nonatomic, copy) NSString *post; +@property (nonatomic, copy) NSString *time; +@property (nonatomic, copy) NSString *media; @property (nonatomic, assign) NSInteger via; @property (nonatomic, assign) NSInteger likes; diff --git a/examples/SocialAppLayout/Sample/Post.m b/examples/SocialAppLayout/Sample/Post.m index 1e3b1fc183..7bedb4e25a 100644 --- a/examples/SocialAppLayout/Sample/Post.m +++ b/examples/SocialAppLayout/Sample/Post.m @@ -12,5 +12,4 @@ #import "Post.h" @implementation Post - @end diff --git a/examples/SocialAppLayout/Sample/PostNode.h b/examples/SocialAppLayout/Sample/PostNode.h index de3dbb80df..486c9184d8 100644 --- a/examples/SocialAppLayout/Sample/PostNode.h +++ b/examples/SocialAppLayout/Sample/PostNode.h @@ -10,28 +10,10 @@ */ #import -#import "Post.h" -@class LikesNode; -@class CommentsNode; +@class Post; -@interface PostNode : ASCellNode { - - Post *_post; - - ASDisplayNode *_divider; - ASTextNode *_nameNode; - ASTextNode *_usernameNode; - ASTextNode *_timeNode; - ASTextNode *_postNode; - ASImageNode *_viaNode; - ASNetworkImageNode *_avatarNode; - ASNetworkImageNode *_mediaNode; - LikesNode *_likesNode; - CommentsNode *_commentsNode; - ASImageNode *_optionsNode; - -} +@interface PostNode : ASCellNode - (instancetype)initWithPost:(Post *)post; diff --git a/examples/SocialAppLayout/Sample/PostNode.m b/examples/SocialAppLayout/Sample/PostNode.m index bf1c5f22b5..fd570dc481 100644 --- a/examples/SocialAppLayout/Sample/PostNode.m +++ b/examples/SocialAppLayout/Sample/PostNode.m @@ -10,53 +10,62 @@ */ #import "PostNode.h" +#import "Post.h" #import "TextStyles.h" #import "LikesNode.h" #import "CommentsNode.h" -@interface PostNode() +@interface PostNode() + +@property (strong, nonatomic) Post *post; +@property (strong, nonatomic) ASDisplayNode *divider; +@property (strong, nonatomic) ASTextNode *nameNode; +@property (strong, nonatomic) ASTextNode *usernameNode; +@property (strong, nonatomic) ASTextNode *timeNode; +@property (strong, nonatomic) ASTextNode *postNode; +@property (strong, nonatomic) ASImageNode *viaNode; +@property (strong, nonatomic) ASNetworkImageNode *avatarNode; +@property (strong, nonatomic) ASNetworkImageNode *mediaNode; +@property (strong, nonatomic) LikesNode *likesNode; +@property (strong, nonatomic) CommentsNode *commentsNode; +@property (strong, nonatomic) ASImageNode *optionsNode; + @end @implementation PostNode -- (instancetype)initWithPost:(Post *)post { - +- (instancetype)initWithPost:(Post *)post +{ self = [super init]; - - if(self) { - + if (self) { _post = post; - // name node + // Name node _nameNode = [[ASTextNode alloc] init]; - _nameNode.attributedString = [[NSAttributedString alloc] initWithString:_post.name - attributes:[TextStyles nameStyle]]; + _nameNode.attributedString = [[NSAttributedString alloc] initWithString:_post.name attributes:[TextStyles nameStyle]]; _nameNode.maximumNumberOfLines = 1; [self addSubnode:_nameNode]; - // username node + // Username node _usernameNode = [[ASTextNode alloc] init]; - _usernameNode.attributedString = [[NSAttributedString alloc] initWithString:_post.username - attributes:[TextStyles usernameStyle]]; + _usernameNode.attributedString = [[NSAttributedString alloc] initWithString:_post.username attributes:[TextStyles usernameStyle]]; _usernameNode.flexShrink = YES; //if name and username don't fit to cell width, allow username shrink _usernameNode.truncationMode = NSLineBreakByTruncatingTail; _usernameNode.maximumNumberOfLines = 1; - [self addSubnode:_usernameNode]; - // time node + // Time node _timeNode = [[ASTextNode alloc] init]; - _timeNode.attributedString = [[NSAttributedString alloc] initWithString:_post.time - attributes:[TextStyles timeStyle]]; + _timeNode.attributedString = [[NSAttributedString alloc] initWithString:_post.time attributes:[TextStyles timeStyle]]; [self addSubnode:_timeNode]; - // post node + // Post node _postNode = [[ASTextNode alloc] init]; - - // processing URLs in post + + // Processing URLs in post NSString *kLinkAttributeName = @"TextLinkAttributeName"; - if(![_post.post isEqualToString:@""]) { + if (![_post.post isEqualToString:@""]) { NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:_post.post attributes:[TextStyles postStyle]]; @@ -75,19 +84,20 @@ }]; - // configure node to support tappable links + // Configure node to support tappable links _postNode.delegate = self; _postNode.userInteractionEnabled = YES; _postNode.linkAttributeNames = @[ kLinkAttributeName ]; _postNode.attributedString = attrString; + _postNode.passthroughNonlinkTouches = YES; // passes touches through when they aren't on a link } [self addSubnode:_postNode]; - // media - if(![_post.media isEqualToString:@""]) { + // Media + if (![_post.media isEqualToString:@""]) { _mediaNode = [[ASNetworkImageNode alloc] init]; _mediaNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); @@ -113,7 +123,7 @@ [self addSubnode:_mediaNode]; } - // user pic + // User pic _avatarNode = [[ASNetworkImageNode alloc] init]; _avatarNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); _avatarNode.preferredFrameSize = CGSizeMake(44, 44); @@ -137,19 +147,19 @@ }; [self addSubnode:_avatarNode]; - // hairline cell separator + // Hairline cell separator _divider = [[ASDisplayNode alloc] init]; _divider.backgroundColor = [UIColor lightGrayColor]; [self addSubnode:_divider]; - if(_post.via != 0) { - + // Via + if (_post.via != 0) { _viaNode = [[ASImageNode alloc] init]; _viaNode.image = (_post.via == 1) ? [UIImage imageNamed:@"icon_ios.png"] : [UIImage imageNamed:@"icon_android.png"]; [self addSubnode:_viaNode]; } - // bottom controls + // Bottom controls _likesNode = [[LikesNode alloc] initWithLikesCount:_post.likes]; [self addSubnode:_likesNode]; @@ -159,9 +169,7 @@ _optionsNode = [[ASImageNode alloc] init]; _optionsNode.image = [UIImage imageNamed:@"icon_more"]; [self addSubnode:_optionsNode]; - } - return self; } @@ -173,52 +181,40 @@ [super didLoad]; } -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { - - //Flexible spacer between username and time +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + // Flexible spacer between username and time ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; spacer.flexGrow = YES; - - //Horizontal stack for name, username, via icon and time - ASStackLayoutSpec *nameStack; - - //Cases with or without via icon - if(_post.via != 0) { - - nameStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:5.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsCenter children:@[_nameNode, _usernameNode, spacer, _viaNode, _timeNode]]; - - }else { - nameStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:5.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsCenter children:@[_nameNode, _usernameNode, spacer, _timeNode]]; + + // NOTE: This inset is not actually required by the layout, but is an example of the upward propogation of layoutable + // properties. Specifically, .flexGrow from the child is transferred to the inset spec so they can expand together. + // Without this capability, it would be required to set insetSpacer.flexGrow = YES; + ASInsetLayoutSpec *insetSpacer = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 0, 0, 0) child:spacer]; + + // Horizontal stack for name, username, via icon and time + NSMutableArray *layoutSpecChildren = [@[_nameNode, _usernameNode, insetSpacer] mutableCopy]; + if (_post.via != 0) { + [layoutSpecChildren addObject:_viaNode]; } + [layoutSpecChildren addObject:_timeNode]; - + ASStackLayoutSpec *nameStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:5.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsCenter children:layoutSpecChildren]; nameStack.alignSelf = ASStackLayoutAlignSelfStretch; // bottom controls horizontal stack ASStackLayoutSpec *controlsStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:10 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsCenter children:@[_likesNode, _commentsNode, _optionsNode]]; - //add more gaps for control line + // Add more gaps for control line controlsStack.spacingAfter = 3.0; controlsStack.spacingBefore = 3.0; NSMutableArray *mainStackContent = [[NSMutableArray alloc] init]; - [mainStackContent addObject:nameStack]; [mainStackContent addObject:_postNode]; - if(![_post.media isEqualToString:@""]) { - - CGFloat imageRatio; - - if(_mediaNode.image != nil) { - - imageRatio = _mediaNode.image.size.height / _mediaNode.image.size.width; - - }else { - - imageRatio = 0.5; - } - + if (![_post.media isEqualToString:@""]) { + CGFloat imageRatio = (_mediaNode.image != nil ? _mediaNode.image.size.height / _mediaNode.image.size.width : 0.5); ASRatioLayoutSpec *imagePlace = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:imageRatio child:_mediaNode]; imagePlace.spacingAfter = 3.0; imagePlace.spacingBefore = 3.0; @@ -226,14 +222,17 @@ [mainStackContent addObject:imagePlace]; } - [mainStackContent addObject:controlsStack]; - //Vertical spec of cell main content + // Vertical spec of cell main content ASStackLayoutSpec *contentSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:8.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart children:mainStackContent]; + contentSpec.alignItems = ASStackLayoutAlignSelfStretch; + contentSpec.flexShrink = YES; + // Horizontal spec for avatar + ASStackLayoutSpec *avatarContentSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:8.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart children:@[_avatarNode, contentSpec]]; - return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 64, 10, 10) child:contentSpec]; + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) child:avatarContentSpec]; } @@ -244,26 +243,23 @@ // Manually layout the divider. CGFloat pixelHeight = 1.0f / [[UIScreen mainScreen] scale]; _divider.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, pixelHeight); - _avatarNode.frame = CGRectMake(10, 10, 44, 44); } -#pragma mark - -#pragma mark ASTextNodeDelegate methods. +#pragma mark - ASTextNodeDelegate methods. - (BOOL)textNode:(ASTextNode *)richTextNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point { - // opt into link highlighting -- tap and hold the link to try it! must enable highlighting on a layer, see -didLoad + // Opt into link highlighting -- tap and hold the link to try it! must enable highlighting on a layer, see -didLoad return YES; } - (void)textNode:(ASTextNode *)richTextNode tappedLinkAttribute:(NSString *)attribute value:(NSURL *)URL atPoint:(CGPoint)point textRange:(NSRange)textRange { - // the node tapped a link, open it + // The node tapped a link, open it [[UIApplication sharedApplication] openURL:URL]; } -#pragma mark - -#pragma mark ASNetworkImageNodeDelegate methods. +#pragma mark - ASNetworkImageNodeDelegate methods. - (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image { diff --git a/examples/SocialAppLayout/Sample/TextStyles.m b/examples/SocialAppLayout/Sample/TextStyles.m index a561469fe3..cc2b6bdffb 100644 --- a/examples/SocialAppLayout/Sample/TextStyles.m +++ b/examples/SocialAppLayout/Sample/TextStyles.m @@ -16,58 +16,58 @@ + (NSDictionary *)nameStyle { return @{ - NSFontAttributeName : [UIFont boldSystemFontOfSize:15.0], - NSForegroundColorAttributeName: [UIColor blackColor] + NSFontAttributeName : [UIFont boldSystemFontOfSize:15.0], + NSForegroundColorAttributeName: [UIColor blackColor] }; } + (NSDictionary *)usernameStyle { return @{ - NSFontAttributeName : [UIFont systemFontOfSize:13.0], - NSForegroundColorAttributeName: [UIColor lightGrayColor] - }; + NSFontAttributeName : [UIFont systemFontOfSize:13.0], + NSForegroundColorAttributeName: [UIColor lightGrayColor] + }; } + (NSDictionary *)timeStyle { return @{ - NSFontAttributeName : [UIFont systemFontOfSize:13.0], - NSForegroundColorAttributeName: [UIColor grayColor] - }; + NSFontAttributeName : [UIFont systemFontOfSize:13.0], + NSForegroundColorAttributeName: [UIColor grayColor] + }; } + (NSDictionary *)postStyle { return @{ - NSFontAttributeName : [UIFont systemFontOfSize:15.0], - NSForegroundColorAttributeName: [UIColor blackColor] - }; + NSFontAttributeName : [UIFont systemFontOfSize:15.0], + NSForegroundColorAttributeName: [UIColor blackColor] + }; } + (NSDictionary *)postLinkStyle { return @{ - NSFontAttributeName : [UIFont systemFontOfSize:15.0], - NSForegroundColorAttributeName: [UIColor colorWithRed:59.0/255.0 green:89.0/255.0 blue:152.0/255.0 alpha:1.0], - NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle) - }; + NSFontAttributeName : [UIFont systemFontOfSize:15.0], + NSForegroundColorAttributeName: [UIColor colorWithRed:59.0/255.0 green:89.0/255.0 blue:152.0/255.0 alpha:1.0], + NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle) + }; } -+ (NSDictionary *)cellControlStyle { - ++ (NSDictionary *)cellControlStyle +{ return @{ - NSFontAttributeName : [UIFont systemFontOfSize:13.0], - NSForegroundColorAttributeName: [UIColor lightGrayColor] - }; + NSFontAttributeName : [UIFont systemFontOfSize:13.0], + NSForegroundColorAttributeName: [UIColor lightGrayColor] + }; } -+ (NSDictionary *)cellControlColoredStyle { - ++ (NSDictionary *)cellControlColoredStyle +{ return @{ - NSFontAttributeName : [UIFont systemFontOfSize:13.0], - NSForegroundColorAttributeName: [UIColor colorWithRed:59.0/255.0 green:89.0/255.0 blue:152.0/255.0 alpha:1.0] - }; + NSFontAttributeName : [UIFont systemFontOfSize:13.0], + NSForegroundColorAttributeName: [UIColor colorWithRed:59.0/255.0 green:89.0/255.0 blue:152.0/255.0 alpha:1.0] + }; } @end diff --git a/examples/SocialAppLayout/Sample/ViewController.h b/examples/SocialAppLayout/Sample/ViewController.h index 7352ff1109..76b893d103 100644 --- a/examples/SocialAppLayout/Sample/ViewController.h +++ b/examples/SocialAppLayout/Sample/ViewController.h @@ -12,7 +12,4 @@ #import @interface ViewController : UIViewController - - @end - diff --git a/examples/SocialAppLayout/Sample/ViewController.m b/examples/SocialAppLayout/Sample/ViewController.m index ff6abbd3f8..c9500b1778 100644 --- a/examples/SocialAppLayout/Sample/ViewController.m +++ b/examples/SocialAppLayout/Sample/ViewController.m @@ -15,55 +15,50 @@ #import #import + #include @interface ViewController () -{ - ASTableView *_tableView; - - NSMutableArray *_socialAppDataSource; -} +@property (nonatomic, strong) ASTableView *tableView; +@property (nonatomic, strong) NSMutableArray *socialAppDataSource; @end + @implementation ViewController - (instancetype)init { - if (!(self = [super init])) - return nil; - - _tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain asyncDataFetching:YES]; - _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; // SocialAppNode has its own separator - _tableView.asyncDataSource = self; - _tableView.asyncDelegate = self; - - [self createSocialAppDataSource]; - - self.title = @"Timeline"; - + self = [super init]; + if (self) { + self.title = @"Timeline"; + [self createSocialAppDataSource]; + } return self; } -- (void)viewDidLoad { - + +- (void)dealloc +{ + _tableView.asyncDataSource = nil; + _tableView.asyncDelegate = nil; +} + +- (void)viewDidLoad +{ [super viewDidLoad]; - [self.view addSubview:_tableView]; -} - -- (void)viewWillLayoutSubviews -{ - _tableView.frame = self.view.bounds; -} - -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - -- (void)createSocialAppDataSource { + self.tableView = [[ASTableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain asyncDataFetching:YES]; + self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; // SocialAppNode has its own separator + self.tableView.asyncDataSource = self; + self.tableView.asyncDelegate = self; + [self.view addSubview:self.tableView]; +} + +- (void)createSocialAppDataSource +{ _socialAppDataSource = [[NSMutableArray alloc] init]; Post *newPost = [[Post alloc] init]; @@ -76,7 +71,6 @@ newPost.via = 0; newPost.likes = arc4random_uniform(74); newPost.comments = arc4random_uniform(40); - [_socialAppDataSource addObject:newPost]; newPost = [[Post alloc] init]; @@ -89,7 +83,6 @@ newPost.via = 1; newPost.likes = arc4random_uniform(74); newPost.comments = arc4random_uniform(40); - [_socialAppDataSource addObject:newPost]; newPost = [[Post alloc] init]; @@ -102,7 +95,6 @@ newPost.via = 2; newPost.likes = arc4random_uniform(74); newPost.comments = arc4random_uniform(40); - [_socialAppDataSource addObject:newPost]; newPost = [[Post alloc] init]; @@ -115,29 +107,28 @@ newPost.via = 1; newPost.likes = arc4random_uniform(74); newPost.comments = arc4random_uniform(40); - [_socialAppDataSource addObject:newPost]; } -#pragma mark - -#pragma mark ASTableView. +#pragma mark - ASTableView -- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { - Post *post = _socialAppDataSource[indexPath.row]; - PostNode *node = [[PostNode alloc] initWithPost:post]; - return node; + Post *post = self.socialAppDataSource[indexPath.row]; + return ^{ + return [[PostNode alloc] initWithPost:post]; + }; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return _socialAppDataSource.count; + return self.socialAppDataSource.count; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { PostNode *postNode = (PostNode *)[_tableView nodeForRowAtIndexPath:indexPath]; - Post *post = _socialAppDataSource[indexPath.row]; + Post *post = self.socialAppDataSource[indexPath.row]; BOOL shouldRasterize = postNode.shouldRasterizeDescendants; shouldRasterize = !shouldRasterize; diff --git a/examples/Swift/Sample/ViewController.swift b/examples/Swift/Sample/ViewController.swift index dec8710f86..84318e2b5e 100644 --- a/examples/Swift/Sample/ViewController.swift +++ b/examples/Swift/Sample/ViewController.swift @@ -41,10 +41,6 @@ final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate fatalError("storyboards are incompatible with truth and beauty") } - override func prefersStatusBarHidden() -> Bool { - return true - } - // MARK: ASTableView data source and delegate. func tableView(tableView: ASTableView, nodeForRowAtIndexPath indexPath: NSIndexPath) -> ASCellNode { diff --git a/examples/SynchronousConcurrency/Sample/AsyncTableViewController.m b/examples/SynchronousConcurrency/Sample/AsyncTableViewController.m index 090805234c..80efd81209 100644 --- a/examples/SynchronousConcurrency/Sample/AsyncTableViewController.m +++ b/examples/SynchronousConcurrency/Sample/AsyncTableViewController.m @@ -68,11 +68,6 @@ _tableView.frame = self.view.bounds; } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - #pragma mark - #pragma mark ASTableView. diff --git a/examples/SynchronousKittens/Sample/ViewController.m b/examples/SynchronousKittens/Sample/ViewController.m index 5d0b594879..9b39e79e0a 100644 --- a/examples/SynchronousKittens/Sample/ViewController.m +++ b/examples/SynchronousKittens/Sample/ViewController.m @@ -103,11 +103,6 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell _tableView.frame = self.view.bounds; } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - - (void)toggleEditingMode { [_tableView setEditing:!_tableView.editing animated:YES]; diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m index 0e7fa9a317..71647da9b0 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m @@ -61,11 +61,6 @@ _pagerNode.frame = self.view.bounds; } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - #pragma mark - #pragma mark ASPagerNode. diff --git a/examples/VideoTableView/Sample/NicCageNode.mm b/examples/VideoTableView/Sample/NicCageNode.mm index c2277762ed..4ca2733522 100644 --- a/examples/VideoTableView/Sample/NicCageNode.mm +++ b/examples/VideoTableView/Sample/NicCageNode.mm @@ -80,16 +80,43 @@ static const CGFloat kInnerPadding = 10.0f; return nil; _kittenSize = size; - - _videoNode = [[ASVideoNode alloc] init]; -// _videoNode.shouldAutoplay = YES; + + u_int32_t videoInitMethod = arc4random_uniform(3); + u_int32_t autoPlay = arc4random_uniform(2); + NSArray* methodArray = @[@"AVAsset", @"File URL", @"HLS URL"]; + NSArray* autoPlayArray = @[@"paused", @"auto play"]; + + switch (videoInitMethod) { + case 0: + // Construct an AVAsset from a URL + _videoNode = [[ASVideoNode alloc] init]; + _videoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-753fe655-86bb-46da-89b7-aa59c60e49c0-niccage.mp4"]]; + break; + + case 1: + // Construct the video node directly from the .mp4 URL + _videoNode = [[ASVideoNode alloc] init]; + _videoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-753fe655-86bb-46da-89b7-aa59c60e49c0-niccage.mp4"]]; + break; + + case 2: + // Construct the video node from an HTTP Live Streaming URL + // URL from https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/02_Playback.html + _videoNode = [[ASVideoNode alloc] init]; + _videoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"]]; + break; + } + + if (autoPlay == 1) + _videoNode.shouldAutoplay = YES; + + _videoNode.shouldAutorepeat = YES; _videoNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); - _videoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-753fe655-86bb-46da-89b7-aa59c60e49c0-niccage.mp4"]]; [self addSubnode:_videoNode]; _textNode = [[ASTextNode alloc] init]; - _textNode.attributedString = [[NSAttributedString alloc] initWithString:[self kittyIpsum] + _textNode.attributedString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ %@ %@", methodArray[videoInitMethod], autoPlayArray[autoPlay], [self kittyIpsum]] attributes:[self textStyle]]; [self addSubnode:_textNode]; diff --git a/examples/VideoTableView/Sample/ViewController.m b/examples/VideoTableView/Sample/ViewController.m index a8fea2c881..062433ba4a 100644 --- a/examples/VideoTableView/Sample/ViewController.m +++ b/examples/VideoTableView/Sample/ViewController.m @@ -98,11 +98,6 @@ static const NSInteger kMaxCageSize = 100; // max number of Cage cells al _tableView.frame = self.view.bounds; } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - - (void)toggleEditingMode { [_tableView setEditing:!_tableView.editing animated:YES]; diff --git a/examples/Videos/Sample/ViewController.m b/examples/Videos/Sample/ViewController.m index 5c4c0488ef..74e4ced00f 100644 --- a/examples/Videos/Sample/ViewController.m +++ b/examples/Videos/Sample/ViewController.m @@ -107,9 +107,4 @@ } } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - @end diff --git a/smoke-tests/Framework/Sample/ViewController.swift b/smoke-tests/Framework/Sample/ViewController.swift index 43db9fb420..b21fa5e201 100644 --- a/smoke-tests/Framework/Sample/ViewController.swift +++ b/smoke-tests/Framework/Sample/ViewController.swift @@ -41,10 +41,6 @@ class ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelega self.tableView.frame = self.view.bounds } - override func prefersStatusBarHidden() -> Bool { - return true - } - // MARK: ASTableView data source and delegate. diff --git a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.m b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.m index 4445a8a026..994d827dcc 100644 --- a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.m +++ b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.m @@ -27,9 +27,4 @@ [self.view addSubnode:_textNode]; } -- (BOOL)prefersStatusBarHidden -{ - return YES; -} - @end