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 ()