diff --git a/.buckconfig b/.buckconfig new file mode 100644 index 0000000000..821cb2c82f --- /dev/null +++ b/.buckconfig @@ -0,0 +1,22 @@ +[cxx] + default_platform = iphonesimulator-x86_64 + combined_preprocess_and_compile = true + +[apple] + iphonesimulator_target_sdk_version = 8.0 + iphoneos_target_sdk_version = 8.0 + xctool_default_destination_specifier = platform=iOS Simulator, name=iPhone 6, OS=10.2 + +[alias] + lib = //:AsyncDisplayKit + tests = //:Tests + +[httpserver] + port = 8080 + +[project] + ide = xcode + ignore = .buckd, \ + .hg, \ + .git, \ + buck-out, \ diff --git a/.buckversion b/.buckversion new file mode 100644 index 0000000000..437aedac09 --- /dev/null +++ b/.buckversion @@ -0,0 +1 @@ +f399f484bf13b47bcc2bf0f2e092ab5d8de9f6e6 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 03194fca3b..c6e5d8efcc 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,6 @@ +// If you're looking for help, please consider joining our slack channel: +// http://asyncdisplaykit.org/slack + // The more information you include, the faster we can help you out! // Please include: a sample project or screenshots, code snippets // AsyncDisplayKit version, and/or backtraces for any crashes (> bt all). diff --git a/.gitignore b/.gitignore index 7669d5a445..adb7e5eecc 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,8 @@ build timeline.xctimeline playground.xcworkspace +# Buck +/buck-out +/.buckconfig.local +/.buckd + diff --git a/.travis.yml b/.travis.yml index 7502716d88..b493cff43c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ before_install: install: echo "<3" env: - MODE=tests + - MODE=tests_listkit - MODE=examples-pt1 - MODE=examples-pt2 - MODE=examples-pt3 diff --git a/ASDKListKit/ASDKListKit.xcodeproj/project.pbxproj b/ASDKListKit/ASDKListKit.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d90f13803b --- /dev/null +++ b/ASDKListKit/ASDKListKit.xcodeproj/project.pbxproj @@ -0,0 +1,394 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + BD860CAB842324FDF0B3105C /* libPods-ASDKListKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FEACA22D54B3609C19BB4CE /* libPods-ASDKListKitTests.a */; }; + CC5532391E16F2A90011C01F /* ASListTestSupplementarySource.m in Sources */ = {isa = PBXBuildFile; fileRef = CC55322D1E16F2A90011C01F /* ASListTestSupplementarySource.m */; }; + CC55323A1E16F2A90011C01F /* ASListTestSupplementaryNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC55322F1E16F2A90011C01F /* ASListTestSupplementaryNode.m */; }; + CC55323B1E16F2A90011C01F /* ASListKitTestAdapterDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5532311E16F2A90011C01F /* ASListKitTestAdapterDataSource.m */; }; + CC55323C1E16F2A90011C01F /* ASListTestSection.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5532331E16F2A90011C01F /* ASListTestSection.m */; }; + CC55323D1E16F2A90011C01F /* ASListTestCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5532351E16F2A90011C01F /* ASListTestCellNode.m */; }; + CC55323E1E16F2A90011C01F /* ASListTestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5532371E16F2A90011C01F /* ASListTestObject.m */; }; + CC55323F1E16F2A90011C01F /* ASListKitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5532381E16F2A90011C01F /* ASListKitTests.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1FEACA22D54B3609C19BB4CE /* libPods-ASDKListKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ASDKListKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + B1FDA57F88BB590E403D7BB8 /* Pods-ASDKListKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ASDKListKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ASDKListKitTests/Pods-ASDKListKitTests.debug.xcconfig"; sourceTree = ""; }; + CC5532231E16EB9D0011C01F /* ASDKListKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ASDKListKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + CC5532281E16EB9D0011C01F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CC55322C1E16F2A90011C01F /* ASListTestSupplementarySource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASListTestSupplementarySource.h; sourceTree = ""; }; + CC55322D1E16F2A90011C01F /* ASListTestSupplementarySource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASListTestSupplementarySource.m; sourceTree = ""; }; + CC55322E1E16F2A90011C01F /* ASListTestSupplementaryNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASListTestSupplementaryNode.h; sourceTree = ""; }; + CC55322F1E16F2A90011C01F /* ASListTestSupplementaryNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASListTestSupplementaryNode.m; sourceTree = ""; }; + CC5532301E16F2A90011C01F /* ASListKitTestAdapterDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASListKitTestAdapterDataSource.h; sourceTree = ""; }; + CC5532311E16F2A90011C01F /* ASListKitTestAdapterDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASListKitTestAdapterDataSource.m; sourceTree = ""; }; + CC5532321E16F2A90011C01F /* ASListTestSection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASListTestSection.h; sourceTree = ""; }; + CC5532331E16F2A90011C01F /* ASListTestSection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASListTestSection.m; sourceTree = ""; }; + CC5532341E16F2A90011C01F /* ASListTestCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASListTestCellNode.h; sourceTree = ""; }; + CC5532351E16F2A90011C01F /* ASListTestCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASListTestCellNode.m; sourceTree = ""; }; + CC5532361E16F2A90011C01F /* ASListTestObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASListTestObject.h; sourceTree = ""; }; + CC5532371E16F2A90011C01F /* ASListTestObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASListTestObject.m; sourceTree = ""; }; + CC5532381E16F2A90011C01F /* ASListKitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASListKitTests.m; sourceTree = ""; }; + CC55326C1E16F67A0011C01F /* ASXCTExtensions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASXCTExtensions.h; sourceTree = ""; }; + D6BDED6F23A72F40F571EEF0 /* Pods-ASDKListKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ASDKListKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ASDKListKitTests/Pods-ASDKListKitTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + CC5532201E16EB9D0011C01F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + BD860CAB842324FDF0B3105C /* libPods-ASDKListKitTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 72C6817154F7AC11E373624A /* Pods */ = { + isa = PBXGroup; + children = ( + B1FDA57F88BB590E403D7BB8 /* Pods-ASDKListKitTests.debug.xcconfig */, + D6BDED6F23A72F40F571EEF0 /* Pods-ASDKListKitTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 74DAA5F5D522433F103348B7 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1FEACA22D54B3609C19BB4CE /* libPods-ASDKListKitTests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + CC5532181E16EB7A0011C01F = { + isa = PBXGroup; + children = ( + CC5532251E16EB9D0011C01F /* ASDKListKitTests */, + CC5532241E16EB9D0011C01F /* Products */, + 72C6817154F7AC11E373624A /* Pods */, + 74DAA5F5D522433F103348B7 /* Frameworks */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + CC5532241E16EB9D0011C01F /* Products */ = { + isa = PBXGroup; + children = ( + CC5532231E16EB9D0011C01F /* ASDKListKitTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + CC5532251E16EB9D0011C01F /* ASDKListKitTests */ = { + isa = PBXGroup; + children = ( + CC55326D1E16F67D0011C01F /* Common */, + CC55326E1E170A740011C01F /* ListKit Fixtures */, + CC5532381E16F2A90011C01F /* ASListKitTests.m */, + CC5532281E16EB9D0011C01F /* Info.plist */, + ); + path = ASDKListKitTests; + sourceTree = ""; + }; + CC55326D1E16F67D0011C01F /* Common */ = { + isa = PBXGroup; + children = ( + CC55326C1E16F67A0011C01F /* ASXCTExtensions.h */, + ); + name = Common; + sourceTree = ""; + }; + CC55326E1E170A740011C01F /* ListKit Fixtures */ = { + isa = PBXGroup; + children = ( + CC55322C1E16F2A90011C01F /* ASListTestSupplementarySource.h */, + CC55322D1E16F2A90011C01F /* ASListTestSupplementarySource.m */, + CC55322E1E16F2A90011C01F /* ASListTestSupplementaryNode.h */, + CC55322F1E16F2A90011C01F /* ASListTestSupplementaryNode.m */, + CC5532301E16F2A90011C01F /* ASListKitTestAdapterDataSource.h */, + CC5532311E16F2A90011C01F /* ASListKitTestAdapterDataSource.m */, + CC5532321E16F2A90011C01F /* ASListTestSection.h */, + CC5532331E16F2A90011C01F /* ASListTestSection.m */, + CC5532341E16F2A90011C01F /* ASListTestCellNode.h */, + CC5532351E16F2A90011C01F /* ASListTestCellNode.m */, + CC5532361E16F2A90011C01F /* ASListTestObject.h */, + CC5532371E16F2A90011C01F /* ASListTestObject.m */, + ); + name = "ListKit Fixtures"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + CC5532221E16EB9D0011C01F /* ASDKListKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = CC5532291E16EB9D0011C01F /* Build configuration list for PBXNativeTarget "ASDKListKitTests" */; + buildPhases = ( + 614B24BFF3DA58512D2E2147 /* [CP] Check Pods Manifest.lock */, + CC55321F1E16EB9D0011C01F /* Sources */, + CC5532201E16EB9D0011C01F /* Frameworks */, + CC5532211E16EB9D0011C01F /* Resources */, + 989E6C194A1983B8B21AB82F /* [CP] Embed Pods Frameworks */, + 876CE14CAF6A87E34577E157 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ASDKListKitTests; + productName = ASDKListKitTests; + productReference = CC5532231E16EB9D0011C01F /* ASDKListKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + CC5532191E16EB7A0011C01F /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0820; + TargetAttributes = { + CC5532221E16EB9D0011C01F = { + CreatedOnToolsVersion = 8.2.1; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = CC55321C1E16EB7A0011C01F /* Build configuration list for PBXProject "ASDKListKit" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = CC5532181E16EB7A0011C01F; + productRefGroup = CC5532241E16EB9D0011C01F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + CC5532221E16EB9D0011C01F /* ASDKListKitTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + CC5532211E16EB9D0011C01F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 614B24BFF3DA58512D2E2147 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 876CE14CAF6A87E34577E157 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ASDKListKitTests/Pods-ASDKListKitTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 989E6C194A1983B8B21AB82F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ASDKListKitTests/Pods-ASDKListKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + CC55321F1E16EB9D0011C01F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CC55323E1E16F2A90011C01F /* ASListTestObject.m in Sources */, + CC5532391E16F2A90011C01F /* ASListTestSupplementarySource.m in Sources */, + CC55323D1E16F2A90011C01F /* ASListTestCellNode.m in Sources */, + CC55323B1E16F2A90011C01F /* ASListKitTestAdapterDataSource.m in Sources */, + CC55323C1E16F2A90011C01F /* ASListTestSection.m in Sources */, + CC55323F1E16F2A90011C01F /* ASListKitTests.m in Sources */, + CC55323A1E16F2A90011C01F /* ASListTestSupplementaryNode.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + CC55321D1E16EB7A0011C01F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Debug; + }; + CC55321E1E16EB7A0011C01F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Release; + }; + CC55322A1E16EB9D0011C01F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B1FDA57F88BB590E403D7BB8 /* Pods-ASDKListKitTests.debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = ASDKListKitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = asyncdisplaykit.ASDKListKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + }; + name = Debug; + }; + CC55322B1E16EB9D0011C01F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D6BDED6F23A72F40F571EEF0 /* Pods-ASDKListKitTests.release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = ASDKListKitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = asyncdisplaykit.ASDKListKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + CC55321C1E16EB7A0011C01F /* Build configuration list for PBXProject "ASDKListKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CC55321D1E16EB7A0011C01F /* Debug */, + CC55321E1E16EB7A0011C01F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CC5532291E16EB9D0011C01F /* Build configuration list for PBXNativeTarget "ASDKListKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CC55322A1E16EB9D0011C01F /* Debug */, + CC55322B1E16EB9D0011C01F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = CC5532191E16EB7A0011C01F /* Project object */; +} diff --git a/ASDKListKit/ASDKListKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ASDKListKit/ASDKListKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..5f9c1bf23a --- /dev/null +++ b/ASDKListKit/ASDKListKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/ASDKTube/Sample.xcworkspace/contents.xcworkspacedata b/ASDKListKit/ASDKListKit.xcworkspace/contents.xcworkspacedata similarity index 79% rename from examples/ASDKTube/Sample.xcworkspace/contents.xcworkspacedata rename to ASDKListKit/ASDKListKit.xcworkspace/contents.xcworkspacedata index 7b5a2f3050..4d0db5485c 100644 --- a/examples/ASDKTube/Sample.xcworkspace/contents.xcworkspacedata +++ b/ASDKListKit/ASDKListKit.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,7 @@ + location = "group:ASDKListKit.xcodeproj"> diff --git a/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.h b/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.h new file mode 100644 index 0000000000..cbace9a735 --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.h @@ -0,0 +1,16 @@ +// +// ASListKitTestAdapterDataSource.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface ASListKitTestAdapterDataSource : NSObject + +// array of numbers which is then passed to -[IGListTestSection setItems:] +@property (nonatomic, strong) NSArray *objects; + +@end diff --git a/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.m b/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.m new file mode 100644 index 0000000000..e83dbf8870 --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.m @@ -0,0 +1,30 @@ +// +// ASListKitTestAdapterDataSource.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASListKitTestAdapterDataSource.h" +#import "ASListTestSection.h" + +@implementation ASListKitTestAdapterDataSource + +- (NSArray *)objectsForListAdapter:(IGListAdapter *)listAdapter +{ + return self.objects; +} + +- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object +{ + ASListTestSection *section = [[ASListTestSection alloc] init]; + return section; +} + +- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter +{ + return nil; +} + +@end diff --git a/ASDKListKit/ASDKListKitTests/ASListKitTests.m b/ASDKListKit/ASDKListKitTests/ASListKitTests.m new file mode 100644 index 0000000000..5312b10874 --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListKitTests.m @@ -0,0 +1,110 @@ +// +// ASListKitTests.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import +#import "ASListKitTestAdapterDataSource.h" +#import "ASXCTExtensions.h" +#import + +@interface ASListKitTests : XCTestCase + +@property (nonatomic, strong) ASCollectionNode *collectionNode; +@property (nonatomic, strong) UICollectionView *collectionView; +@property (nonatomic, strong) IGListAdapter *adapter; +@property (nonatomic, strong) ASListKitTestAdapterDataSource *dataSource; +@property (nonatomic, strong) UICollectionViewFlowLayout *layout; +@property (nonatomic, strong) UIWindow *window; +@property (nonatomic) NSInteger reloadDataCount; + +@end + +@implementation ASListKitTests + +- (void)setUp +{ + [super setUp]; + + [ASCollectionView swizzleInstanceMethod:@selector(reloadData) withReplacement:JGMethodReplacementProviderBlock { + return JGMethodReplacement(void, ASCollectionView *) { + JGOriginalImplementation(void); + _reloadDataCount++; + }; + }]; + + self.window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + + self.layout = [[UICollectionViewFlowLayout alloc] init]; + self.collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:self.layout]; + self.collectionNode.frame = self.window.bounds; + self.collectionView = self.collectionNode.view; + + [self.window addSubnode:self.collectionNode]; + + IGListAdapterUpdater *updater = [[IGListAdapterUpdater alloc] init]; + + self.dataSource = [[ASListKitTestAdapterDataSource alloc] init]; + self.adapter = [[IGListAdapter alloc] initWithUpdater:updater + viewController:nil + workingRangeSize:0]; + self.adapter.dataSource = self.dataSource; + [self.adapter setASDKCollectionNode:self.collectionNode]; + XCTAssertNotNil(self.adapter.collectionView, @"Adapter was not bound to collection view. You may have a stale copy of AsyncDisplayKit that was built without IG_LIST_KIT. Clean Builder Folder IMO."); +} + +- (void)tearDown +{ + [super tearDown]; + XCTAssert([ASCollectionView deswizzleAllMethods]); + self.reloadDataCount = 0; + self.window = nil; + self.collectionNode = nil; + self.collectionView = nil; + self.adapter = nil; + self.dataSource = nil; + self.layout = nil; +} + +- (void)test_whenAdapterUpdated_withObjectsOverflow_thatVisibleObjectsIsSubsetOfAllObjects +{ + // each section controller returns n items sized 100x10 + self.dataSource.objects = @[@1, @2, @3, @4, @5, @6]; + XCTestExpectation *e = [self expectationWithDescription:@"Data update completed"]; + + [self.adapter performUpdatesAnimated:NO completion:^(BOOL finished) { + [e fulfill]; + }]; + + [self waitForExpectationsWithTimeout:1 handler:nil]; + self.collectionNode.view.contentOffset = CGPointMake(0, 30); + [self.collectionNode.view layoutIfNeeded]; + + + NSArray *visibleObjects = [[self.adapter visibleObjects] sortedArrayUsingSelector:@selector(compare:)]; + NSArray *expectedObjects = @[@3, @4, @5]; + XCTAssertEqualObjects(visibleObjects, expectedObjects); +} + +- (void)test_whenCollectionViewIsNotInAWindow_updaterDoesNotJustCallReloadData +{ + [self.collectionView removeFromSuperview]; + + [self.collectionView layoutIfNeeded]; + self.dataSource.objects = @[@1, @2, @3, @4, @5, @6]; + XCTestExpectation *e = [self expectationWithDescription:@"Data update completed"]; + + [self.adapter performUpdatesAnimated:NO completion:^(BOOL finished) { + [e fulfill]; + }]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + [self.collectionView layoutIfNeeded]; + + XCTAssertEqual(self.reloadDataCount, 2); +} + +@end diff --git a/ASDKListKit/ASDKListKitTests/ASListTestCellNode.h b/ASDKListKit/ASDKListKitTests/ASListTestCellNode.h new file mode 100644 index 0000000000..b94de37c62 --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListTestCellNode.h @@ -0,0 +1,13 @@ +// +// ASListTestCellNode.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface ASListTestCellNode : ASCellNode + +@end diff --git a/ASDKListKit/ASDKListKitTests/ASListTestCellNode.m b/ASDKListKit/ASDKListKitTests/ASListTestCellNode.m new file mode 100644 index 0000000000..fd82e6dc4f --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListTestCellNode.m @@ -0,0 +1,13 @@ +// +// ASListTestCellNode.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASListTestCellNode.h" + +@implementation ASListTestCellNode + +@end diff --git a/ASDKListKit/ASDKListKitTests/ASListTestObject.h b/ASDKListKit/ASDKListKitTests/ASListTestObject.h new file mode 100644 index 0000000000..8b5cd23bb5 --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListTestObject.h @@ -0,0 +1,22 @@ +// +// ASListTestObject.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASListTestObject : NSObject + +- (instancetype)initWithKey:(id )key value:(id)value; + +@property (nonatomic, strong, readonly) id key; +@property (nonatomic, strong) id value; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ASDKListKit/ASDKListKitTests/ASListTestObject.m b/ASDKListKit/ASDKListKitTests/ASListTestObject.m new file mode 100644 index 0000000000..ffd1216dd4 --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListTestObject.m @@ -0,0 +1,49 @@ +// +// ASListTestObject.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASListTestObject.h" + +@implementation ASListTestObject + +- (instancetype)initWithKey:(id)key value:(id)value +{ + if (self = [super init]) { + _key = [key copy]; + _value = value; + } + return self; +} + +- (instancetype)copyWithZone:(NSZone *)zone +{ + return [[ASListTestObject alloc] initWithKey:self.key value:self.value]; +} + +#pragma mark - IGListDiffable + +- (id)diffIdentifier +{ + return self.key; +} + +- (BOOL)isEqualToDiffableObject:(id)object +{ + if (object == self) { + return YES; + } + if ([object isKindOfClass:[ASListTestObject class]]) { + id k1 = self.key; + id k2 = [object key]; + id v1 = self.value; + id v2 = [(ASListTestObject *)object value]; + return (v1 == v2 || [v1 isEqual:v2]) && (k1 == k2 || [k1 isEqual:k2]); + } + return NO; +} + +@end diff --git a/ASDKListKit/ASDKListKitTests/ASListTestSection.h b/ASDKListKit/ASDKListKitTests/ASListTestSection.h new file mode 100644 index 0000000000..1d10ddbe5b --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListTestSection.h @@ -0,0 +1,18 @@ +// +// ASListTestSection.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import + +@interface ASListTestSection : IGListSectionController + +@property (nonatomic) NSInteger itemCount; + +@property (nonatomic) NSInteger selectedItemIndex; + +@end diff --git a/ASDKListKit/ASDKListKitTests/ASListTestSection.m b/ASDKListKit/ASDKListKitTests/ASListTestSection.m new file mode 100644 index 0000000000..eac14aec0f --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListTestSection.m @@ -0,0 +1,60 @@ +// +// ASListTestSection.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASListTestSection.h" +#import "ASListTestCellNode.h" + +@implementation ASListTestSection + +- (instancetype)init +{ + if (self = [super init]) +{ + _selectedItemIndex = NSNotFound; + } + return self; +} + +- (NSInteger)numberOfItems +{ + return self.itemCount; +} + +- (CGSize)sizeForItemAtIndex:(NSInteger)index +{ + return [ASIGListSectionControllerMethods sizeForItemAtIndex:index]; +} + +- (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index +{ + return [ASIGListSectionControllerMethods cellForItemAtIndex:index sectionController:self]; +} + +- (void)didUpdateToObject:(id)object +{ + if ([object isKindOfClass:[NSNumber class]]) +{ + self.itemCount = [object integerValue]; + } +} + +- (void)didSelectItemAtIndex:(NSInteger)index +{ + self.selectedItemIndex = index; +} + +- (ASCellNodeBlock)nodeBlockForItemAtIndex:(NSInteger)index +{ + return ^{ + ASListTestCellNode *node = [[ASListTestCellNode alloc] init]; + node.style.preferredSize = CGSizeMake(100, 10); + return node; + }; +} + +@end diff --git a/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.h b/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.h new file mode 100644 index 0000000000..55dcd6b70b --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.h @@ -0,0 +1,13 @@ +// +// ASListTestSupplementaryNode.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface ASListTestSupplementaryNode : ASCellNode + +@end diff --git a/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.m b/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.m new file mode 100644 index 0000000000..f47f577ca6 --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.m @@ -0,0 +1,13 @@ +// +// ASListTestSupplementaryNode.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASListTestSupplementaryNode.h" + +@implementation ASListTestSupplementaryNode + +@end diff --git a/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.h b/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.h new file mode 100644 index 0000000000..55f00d4a5a --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.h @@ -0,0 +1,20 @@ +// +// ASListTestSupplementarySource.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import + +@interface ASListTestSupplementarySource : NSObject + +@property (nonatomic, strong, readwrite) NSArray *supportedElementKinds; + +@property (nonatomic, weak) id collectionContext; + +@property (nonatomic, weak) IGListSectionController *sectionController; + +@end diff --git a/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.m b/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.m new file mode 100644 index 0000000000..2184258a53 --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.m @@ -0,0 +1,33 @@ +// +// ASListTestSupplementarySource.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 12/25/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASListTestSupplementarySource.h" +#import "ASListTestSupplementaryNode.h" + +@implementation ASListTestSupplementarySource + +- (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index +{ + return [ASIGListSupplementaryViewSourceMethods viewForSupplementaryElementOfKind:elementKind atIndex:index sectionController:self.sectionController]; +} + +- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index +{ + return [ASIGListSupplementaryViewSourceMethods sizeForSupplementaryViewOfKind:elementKind atIndex:index]; +} + +- (ASCellNodeBlock)nodeBlockForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index +{ + return ^{ + ASListTestSupplementaryNode *node = [[ASListTestSupplementaryNode alloc] init]; + node.style.preferredSize = CGSizeMake(100, 10); + return node; + }; +} + +@end diff --git a/AsyncDisplayKitTests/ASXCTExtensions.h b/ASDKListKit/ASDKListKitTests/ASXCTExtensions.h similarity index 100% rename from AsyncDisplayKitTests/ASXCTExtensions.h rename to ASDKListKit/ASDKListKitTests/ASXCTExtensions.h diff --git a/ASDKListKit/ASDKListKitTests/Info.plist b/ASDKListKit/ASDKListKitTests/Info.plist new file mode 100644 index 0000000000..6c6c23c43a --- /dev/null +++ b/ASDKListKit/ASDKListKitTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/ASDKListKit/Podfile b/ASDKListKit/Podfile new file mode 100644 index 0000000000..9204e224f9 --- /dev/null +++ b/ASDKListKit/Podfile @@ -0,0 +1,9 @@ +source 'https://github.com/CocoaPods/Specs.git' + +platform :ios, '8.0' +target 'ASDKListKitTests' do + pod 'AsyncDisplayKit/IGListKit', :path => '..' + pod 'IGListKit', :git => 'https://github.com/Instagram/IGListKit', :commit => 'e9e09d7' + pod 'JGMethodSwizzler', :git => 'https://github.com/JonasGessner/JGMethodSwizzler', :branch => 'master' +end + diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index e5101b4a85..3f6bf04e34 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -1,19 +1,19 @@ Pod::Spec.new do |spec| spec.name = 'AsyncDisplayKit' - spec.version = '2.0-beta.1' + spec.version = '2.2' spec.license = { :type => 'BSD' } spec.homepage = 'http://asyncdisplaykit.org' spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com' } spec.summary = 'Smooth asynchronous user interfaces for iOS apps.' spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => spec.version.to_s } + spec.deprecated_in_favor_of = 'Texture' spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/' - spec.frameworks = 'AssetsLibrary' - spec.weak_frameworks = 'Photos','MapKit' + spec.weak_frameworks = 'Photos','MapKit','AssetsLibrary' spec.requires_arc = true - spec.ios.deployment_target = '7.0' + spec.ios.deployment_target = '8.0' # Uncomment when fixed: issues with tvOS build for release 2.0 # spec.tvos.deployment_target = '9.0' @@ -21,50 +21,45 @@ Pod::Spec.new do |spec| # Subspecs spec.subspec 'Core' do |core| core.public_header_files = [ - 'AsyncDisplayKit/*.h', - 'AsyncDisplayKit/Details/**/*.h', - 'AsyncDisplayKit/Layout/*.h', - 'Base/*.h', - 'AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.h', - 'AsyncDisplayKit/TextKit/ASTextNodeTypes.h', - 'AsyncDisplayKit/TextKit/ASTextKitComponents.h' + 'Source/*.h', + 'Source/Details/**/*.h', + 'Source/Layout/**/*.h', + 'Source/Base/*.h', + 'Source/Debug/AsyncDisplayKit+Debug.h', + 'Source/TextKit/ASTextNodeTypes.h', + 'Source/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}', + 'Source/**/*.{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 - mrr.source_files = [ - 'AsyncDisplayKit/Private/_AS-objc-internal.h', - 'AsyncDisplayKit/Details/ASDealloc2MainObject.h', - 'AsyncDisplayKit/Details/ASDealloc2MainObject.m', + 'Source/TextKit/*.h', ] + core.xcconfig = { 'GCC_PRECOMPILE_PREFIX_HEADER' => 'YES' } end spec.subspec 'PINRemoteImage' do |pin| - pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } - pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.6' + pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.9' pin.dependency 'PINRemoteImage/PINCache' pin.dependency 'AsyncDisplayKit/Core' end + + spec.subspec 'IGListKit' do |igl| + igl.dependency 'IGListKit', '2.1.0' + igl.dependency 'AsyncDisplayKit/Core' + end + spec.subspec 'Yoga' do |yoga| + yoga.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) YOGA=1' } + yoga.dependency 'Yoga', '1.0.2' + yoga.dependency 'AsyncDisplayKit/Core' + end + # Include optional PINRemoteImage module spec.default_subspec = 'PINRemoteImage' diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index b861e3bba2..dded87d385 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -3,62 +3,28 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 47; objects = { /* Begin PBXBuildFile section */ 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */; }; - 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */; }; 044284FF1BAA3BD600D16268 /* UICollectionViewLayout+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; }; - 044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; }; + 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; settings = {ATTRIBUTES = (Private, ); }; }; 0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; }; - 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; }; - 0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; }; - 044285101BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; }; - 0515EA211A15769900BA8B9A /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 0515EA221A1576A100BA8B9A /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; - 0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */; }; - 051943131A1575630030A7D0 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; - 051943151A1575670030A7D0 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 0442850E1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 044285101BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m */; }; 052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */; }; 052EE06B1A15A0D8002C6279 /* TestResources in Resources */ = {isa = PBXBuildFile; fileRef = 052EE06A1A15A0D8002C6279 /* TestResources */; }; - 0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */; }; - 055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */; }; - 055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3319ABD3E3004DAFF1 /* ASTableView.mm */; }; - 055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */; }; 056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */; }; 057D02C41AC0A66700C7AC3C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C31AC0A66700C7AC3C /* main.m */; }; - 057D02C71AC0A66700C7AC3C /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C61AC0A66700C7AC3C /* AppDelegate.mm */; }; - 0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */; }; - 058D09B0195D04C000B7D73C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; + 057D02C71AC0A66700C7AC3C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C61AC0A66700C7AC3C /* AppDelegate.m */; }; 058D09BE195D04C000B7D73C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09BD195D04C000B7D73C /* XCTest.framework */; }; 058D09BF195D04C000B7D73C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; 058D09C1195D04C000B7D73C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09C0195D04C000B7D73C /* UIKit.framework */; }; - 058D09C4195D04C000B7D73C /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; }; 058D09CA195D04C000B7D73C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 058D09C8195D04C000B7D73C /* InfoPlist.strings */; }; - 058D0A13195D050800B7D73C /* ASControlNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09D6195D050800B7D73C /* ASControlNode.mm */; }; - 058D0A14195D050800B7D73C /* ASDisplayNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09D9195D050800B7D73C /* ASDisplayNode.mm */; }; - 058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */; }; - 058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09DE195D050800B7D73C /* ASImageNode.mm */; }; - 058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E0195D050800B7D73C /* ASTextNode.mm */; }; - 058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */; }; - 058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E5195D050800B7D73C /* _ASDisplayView.mm */; }; - 058D0A1A195D050800B7D73C /* ASHighlightOverlayLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */; }; - 058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */; }; - 058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */; }; - 058D0A22195D050800B7D73C /* _ASAsyncTransaction.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F9195D050800B7D73C /* _ASAsyncTransaction.mm */; }; - 058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */; }; - 058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */; }; - 058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */; }; - 058D0A27195D050800B7D73C /* _ASPendingState.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A06195D050800B7D73C /* _ASPendingState.mm */; }; - 058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */; }; - 058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */; }; - 058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */; }; - 058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */; }; 058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A2D195D057000B7D73C /* ASDisplayLayerTests.m */; }; 058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A2E195D057000B7D73C /* ASDisplayNodeAppearanceTests.m */; }; - 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.m */; }; + 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.mm */; }; 058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.m */; }; 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */; }; 058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */; }; @@ -66,34 +32,25 @@ 058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */; }; 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */; }; 18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; }; 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; }; - 204C979E1B362CB3002B1083 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 204C979D1B362CB3002B1083 /* Default-568h@2x.png */; }; - 205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */; }; - 205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; - 205F0E1A1B37339C007741D0 /* ASAbstractLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */; }; - 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */; }; - 205F0E221B376416007741D0 /* CoreGraphics+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */; }; 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; }; - 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; - 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; }; 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */; }; 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 */; }; + 254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */; settings = {ATTRIBUTES = (Private, ); }; }; 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 */; }; - 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */; }; - 254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */; }; - 254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */; }; - 254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */; }; - 254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; }; - 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; }; - 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; }; - 254C6B821BF94F8A003EC431 /* ASTextKitComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */; }; + 254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754961BEE44CD00737CA5 /* ASTextKitContext.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 254C6B821BF94F8A003EC431 /* ASTextKitComponents.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */; }; 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 */; }; @@ -104,30 +61,17 @@ 254C6B8A1BF94F8A003EC431 /* ASTextKitRenderer+TextChecking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */; }; 254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */; }; 254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; }; - 257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */; }; - 257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754971BEE44CD00737CA5 /* ASTextKitContext.mm */; }; - 257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754991BEE44CD00737CA5 /* ASTextKitEntityAttribute.m */; }; - 257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549A1BEE44CD00737CA5 /* ASTextKitRenderer.mm */; }; - 257754AE1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549C1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm */; }; - 257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */; }; - 257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */; }; - 257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; }; - 257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */; }; - 257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; }; - 257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; }; 25E327571C16819500A2170C /* ASPagerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 25E327581C16819500A2170C /* ASPagerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25E327551C16819500A2170C /* ASPagerNode.m */; }; 25E327591C16819500A2170C /* ASPagerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25E327551C16819500A2170C /* ASPagerNode.m */; }; 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; }; 296A0A351A951ABF005ACEAA /* ASBatchFetchingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */; }; - 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; }; 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */; }; 2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; }; 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED071B17843500DA7C62 /* ASDimension.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED081B17843500DA7C62 /* ASDimension.mm */; }; - 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; }; + 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; 34EFC75E1B701BF000AD841F /* ASInternalHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */; }; 34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */; }; @@ -149,192 +93,123 @@ 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */; }; 34EFC7731B701D0700AD841F /* ASAbsoluteLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED181B17843500DA7C62 /* ASAbsoluteLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED191B17843500DA7C62 /* ASAbsoluteLayoutSpec.mm */; }; - 34EFC7751B701D2400AD841F /* ASStackPositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */; }; - 34EFC7761B701D2A00AD841F /* ASStackPositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */; }; - 34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */; }; - 34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */; }; - 34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */; }; - 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */; }; - 430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 430E7C911B4C23F100697A4C /* ASIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */; }; - 430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */; }; - 464052211A3F83C40061C0BA /* ASDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521A1A3F83C40061C0BA /* ASDataController.mm */; }; - 464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */; }; + 3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */; }; + 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */; }; 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; - 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; }; 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */; }; - 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */; }; + 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; }; + 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */; }; 509E68651B3AEDC5009B9150 /* CoreGraphics+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+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 */; }; 680346941CE4052A0009FEB4 /* ASNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 683489281D70DE3400327501 /* ASDisplayNode+Deprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; 68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; - 68355B3A1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */; }; - 68355B3C1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m */; }; 68355B3E1CB57A60001D4E68 /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */; }; - 68355B3F1CB57A64001D4E68 /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; + 68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DF1CBDB958007E4543 /* ASWeakProxy.h */; }; - 68B8A4E31CBDB958007E4543 /* ASWeakProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 68B8A4E01CBDB958007E4543 /* ASWeakProxy.m */; }; + 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DF1CBDB958007E4543 /* ASWeakProxy.h */; settings = {ATTRIBUTES = (Private, ); }; }; 68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 68B8A4E01CBDB958007E4543 /* ASWeakProxy.m */; }; 68C215581DE10D330019C4BC /* ASCollectionViewLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 68C215561DE10D330019C4BC /* ASCollectionViewLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 68C215591DE10D330019C4BC /* ASCollectionViewLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 68C215571DE10D330019C4BC /* ASCollectionViewLayoutInspector.m */; }; 68C2155A1DE10D330019C4BC /* ASCollectionViewLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 68C215571DE10D330019C4BC /* ASCollectionViewLayoutInspector.m */; }; - 68C2155B1DE11A790019C4BC /* ASCollectionViewLayoutInspector.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68C215561DE10D330019C4BC /* ASCollectionViewLayoutInspector.h */; }; - 68C2155C1DE11AA80019C4BC /* ASObjectDescriptionHelpers.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 6907C2561DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h */; }; - 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */; }; - 68EE0DBF1C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */; }; + 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */; settings = {ATTRIBUTES = (Private, ); }; }; 68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */; }; - 68FC85DF1CE29AB700EDD713 /* ASNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85DD1CE29AB700EDD713 /* ASNavigationController.m */; }; 68FC85E31CE29B7E00EDD713 /* ASTabBarController.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85E01CE29B7E00EDD713 /* ASTabBarController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 68FC85E41CE29B7E00EDD713 /* ASTabBarController.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85E11CE29B7E00EDD713 /* ASTabBarController.m */; }; 68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85E11CE29B7E00EDD713 /* ASTabBarController.m */; }; 68FC85E61CE29B9400EDD713 /* ASNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85DD1CE29AB700EDD713 /* ASNavigationController.m */; }; 68FC85EA1CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 68FC85EB1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */; }; 68FC85EC1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */; }; + 6900C5F41E8072DA00BCD75C /* ASImageNode+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 6900C5F31E8072DA00BCD75C /* ASImageNode+Private.h */; }; 6907C2581DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 6907C2561DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 6907C2591DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */; }; 6907C25A1DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */; }; - 69127CFE1DD2B387004BF6E2 /* ASEventLog.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */; }; - 693117CE1DC7C72700DE4784 /* ASDisplayNode+Deprecated.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */; }; - 69527B121DC84292004785FB /* ASLayoutElementStylePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 69527B111DC84292004785FB /* ASLayoutElementStylePrivate.h */; }; - 6959433E1D70815300B0EE1F /* ASDisplayNodeLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */; }; + 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */; }; + 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 690C35631E055C7B00069B91 /* ASDimensionInternal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 690C35671E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690C35651E0567C600069B91 /* ASDimensionDeprecated.mm */; }; + 690C356B1E05680300069B91 /* ASDimensionDeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = 690C356A1E05680300069B91 /* ASDimensionDeprecated.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 690ED58E1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 690ED58D1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 690ED5961E36D118000627C0 /* ASControlNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 690ED5921E36D118000627C0 /* ASControlNode+tvOS.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 690ED5981E36D118000627C0 /* ASControlNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 690ED5931E36D118000627C0 /* ASControlNode+tvOS.m */; }; + 690ED5991E36D118000627C0 /* ASImageNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 690ED5941E36D118000627C0 /* ASImageNode+tvOS.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 690ED5951E36D118000627C0 /* ASImageNode+tvOS.m */; }; + 692510141E74FB44003F2DD0 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 692510131E74FB44003F2DD0 /* Default-568h@2x.png */; }; + 692BE8D71E36B65B00C86D87 /* ASLayoutSpecPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 692BE8D61E36B65B00C86D87 /* ASLayoutSpecPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 6947B0BE1E36B4E30007C478 /* ASStackUnpositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 6947B0BC1E36B4E30007C478 /* ASStackUnpositionedLayout.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 6947B0C01E36B4E30007C478 /* ASStackUnpositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6947B0BD1E36B4E30007C478 /* ASStackUnpositionedLayout.mm */; }; + 6947B0C31E36B5040007C478 /* ASStackPositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 6947B0C11E36B5040007C478 /* ASStackPositionedLayout.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 6947B0C51E36B5040007C478 /* ASStackPositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6947B0C21E36B5040007C478 /* ASStackPositionedLayout.mm */; }; 6959433F1D70815300B0EE1F /* ASDisplayNodeLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */; }; - 695943401D70815300B0EE1F /* ASDisplayNodeLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */; }; + 695943401D70815300B0EE1F /* ASDisplayNodeLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */; settings = {ATTRIBUTES = (Private, ); }; }; 695BE2551DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */; }; 696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 696F01ED1DD2AF450049FBD5 /* ASEventLog.mm in Sources */ = {isa = PBXBuildFile; fileRef = 696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */; }; 696F01EE1DD2AF450049FBD5 /* ASEventLog.mm in Sources */ = {isa = PBXBuildFile; fileRef = 696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */; }; 696FCB311D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */; }; - 69708BA61D76386D005C3CF9 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 69708BA41D76386D005C3CF9 /* ASEqualityHashHelpers.h */; }; - 69708BA71D76386D005C3CF9 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69708BA51D76386D005C3CF9 /* ASEqualityHashHelpers.mm */; }; - 69708BA81D76386D005C3CF9 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69708BA51D76386D005C3CF9 /* ASEqualityHashHelpers.mm */; }; - 6977965F1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 6977965D1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h */; }; - 697796601D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */; }; + 6977965F1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 6977965D1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h */; settings = {ATTRIBUTES = (Private, ); }; }; 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */; }; 697B315A1CFE4B410049936F /* ASEditableTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 697B31591CFE4B410049936F /* ASEditableTextNodeTests.m */; }; - 698548641CA9E025008A345F /* ASEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = 698548611CA9E025008A345F /* ASEnvironment.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 698371DB1E4379CD00437585 /* ASNodeController+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 698371D91E4379CD00437585 /* ASNodeController+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 698371DC1E4379CD00437585 /* ASNodeController+Beta.m in Sources */ = {isa = PBXBuildFile; fileRef = 698371DA1E4379CD00437585 /* ASNodeController+Beta.m */; }; 698C8B621CAB49FC0052DC3F /* ASLayoutElementExtensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutElementExtensibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 698DFF441E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 698DFF431E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 698DFF461E36B7E9002891F1 /* ASLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; }; 69B225671D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69B225661D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm */; }; - 69C4CAF61DA3147000B1EC9B /* ASLayoutElementStylePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 69C4CAF51DA3147000B1EC9B /* ASLayoutElementStylePrivate.h */; }; - 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; }; - 69CB62AD1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; }; + 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; settings = {ATTRIBUTES = (Private, ); }; }; 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; }; 69E0E8A71D356C9400627613 /* ASEqualityHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 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 */; }; - 69EEA0A11D9AB43900B46420 /* ASLayoutSpecPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 69EEA0A01D9AB43900B46420 /* ASLayoutSpecPrivate.h */; }; 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 69F381A51DA4630D00CF2278 /* NSArray+Diffing.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; 69FEE53D1D95A9AF0086F066 /* ASLayoutElementStyleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 69FEE53C1D95A9AF0086F066 /* ASLayoutElementStyleTests.m */; }; 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 */; }; 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */; }; - 7A06A73A1C35F08800FE8DAA /* ASRelativeLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */; }; 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 */; }; 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8021EC1E1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */; }; - 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; - 83A7D95A1D44542100BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; }; 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; }; - 83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A7D9581D44542100BF333E /* ASWeakMap.h */; }; + 83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A7D9581D44542100BF333E /* ASWeakMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; 83A7D95E1D446A6E00BF333E /* ASWeakMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */; }; - 8B0768B41CE752EC002E1453 /* ASDefaultPlaybackButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */; }; - 8BBBAB8C1CEBAF1700107FC6 /* ASDefaultPlaybackButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */; }; + 8BBBAB8C1CEBAF1700107FC6 /* ASDefaultPlaybackButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */; settings = {ATTRIBUTES = (Private, ); }; }; 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */; }; - 8BDA5FC61CDBDDE1007D13B2 /* ASVideoPlayerNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */; }; - 8BDA5FC71CDBDF91007D13B2 /* ASVideoPlayerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */; }; + 8BDA5FC71CDBDF91007D13B2 /* ASVideoPlayerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */; }; - 92074A621CC8BA1900918F75 /* ASImageNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */; }; - 92074A631CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */; }; - 92074A641CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */; }; - 92074A681CC8BADA00918F75 /* ASControlNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */; }; - 92074A691CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A661CC8BADA00918F75 /* ASControlNode+tvOS.m */; }; - 92074A6A1CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A661CC8BADA00918F75 /* ASControlNode+tvOS.m */; }; - 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; - 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; + 90FC784F1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm in Sources */ = {isa = PBXBuildFile; fileRef = 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */; }; + 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; 92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 92DD2FE91BF4D4870074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 92DD2FEA1BF4D49B0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; - 9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; - 9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; 9C49C3701B853961000B0DD5 /* ASStackLayoutElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 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, ); }; }; 9C6BB3B31B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C70F2041CDA4EFA007D6C76 /* ASTraitCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.m */; }; 9C70F2051CDA4F06007D6C76 /* ASTraitCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.m */; }; 9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C70F2081CDAA3C6007D6C76 /* ASEnvironment.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFFC6BD1CCAC52B006A6476 /* ASEnvironment.mm */; }; 9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFFC6BF1CCAC73C006A6476 /* ASViewController.mm */; }; 9C70F20A1CDBE949007D6C76 /* ASTableNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFFC6C11CCAC768006A6476 /* ASTableNode.mm */; }; - 9C70F20B1CDBE9A4007D6C76 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; }; - 9C70F20C1CDBE9B6007D6C76 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; }; - 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */; }; + 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; }; - 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; - 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; - 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; - 9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */; }; + 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */; }; - 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; }; + 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9CC606651D24DF9E006581A0 /* NSIndexSet+ASHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */; }; 9CDC18CD1B910E12004965E2 /* ASLayoutElementPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutElementPrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9CFFC6BE1CCAC52B006A6476 /* ASEnvironment.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFFC6BD1CCAC52B006A6476 /* ASEnvironment.mm */; }; - 9CFFC6C01CCAC73C006A6476 /* ASViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFFC6BF1CCAC73C006A6476 /* ASViewController.mm */; }; - 9CFFC6C21CCAC768006A6476 /* ASTableNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFFC6C11CCAC768006A6476 /* ASTableNode.mm */; }; 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */; }; - 9F98C0251DBDF2A300476D92 /* ASControlTargetAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F98C0241DBDF2A300476D92 /* ASControlTargetAction.m */; }; 9F98C0261DBE29E000476D92 /* ASControlTargetAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F98C0241DBDF2A300476D92 /* ASControlTargetAction.m */; }; - 9F98C0271DBE29FC00476D92 /* ASControlTargetAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F98C0231DBDF2A300476D92 /* ASControlTargetAction.h */; }; - A2763D7A1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */; }; + 9F98C0271DBE29FC00476D92 /* ASControlTargetAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F98C0231DBDF2A300476D92 /* ASControlTargetAction.h */; settings = {ATTRIBUTES = (Private, ); }; }; + A2763D7A1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; }; A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC026B581BD3F61800BBC17E /* ASAbsoluteLayoutSpecSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B571BD3F61800BBC17E /* ASAbsoluteLayoutSpecSnapshotTests.m */; }; - AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */; }; - AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */; }; - AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */; }; - AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm */; }; + AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */; settings = {ATTRIBUTES = (Private, ); }; }; AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm */; }; - AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A501A1139C100143C57 /* ASCollectionView.mm */; }; AC47D9421B3B891B00AAEE9D /* ASCellNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC6456071B0A335000CF11B8 /* ASCellNode.mm */; }; - AC6145411D8AFAE8003D62A2 /* ASSection.h in Headers */ = {isa = PBXBuildFile; fileRef = AC6145401D8AFAE8003D62A2 /* ASSection.h */; }; - AC6145431D8AFD4F003D62A2 /* ASSection.m in Sources */ = {isa = PBXBuildFile; fileRef = AC6145421D8AFD4F003D62A2 /* ASSection.m */; }; + AC6145411D8AFAE8003D62A2 /* ASSection.h in Headers */ = {isa = PBXBuildFile; fileRef = AC6145401D8AFAE8003D62A2 /* ASSection.h */; settings = {ATTRIBUTES = (Private, ); }; }; AC6145441D8AFD4F003D62A2 /* ASSection.m in Sources */ = {isa = PBXBuildFile; fileRef = AC6145421D8AFD4F003D62A2 /* ASSection.m */; }; - AC6456091B0A335000CF11B8 /* ASCellNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC6456071B0A335000CF11B8 /* ASCellNode.mm */; }; - AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */; }; + AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; ACE87A2C1D73696800D7FF06 /* ASSectionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = ACE87A2B1D73696800D7FF06 /* ASSectionContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; - ACE87A331D73726300D7FF06 /* ASSectionContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACE87A2B1D73696800D7FF06 /* ASSectionContext.h */; }; - ACF6ED1B1B17843500DA7C62 /* ASBackgroundLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */; }; - ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */; }; - ACF6ED211B17843500DA7C62 /* ASDimension.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED081B17843500DA7C62 /* ASDimension.mm */; }; - ACF6ED231B17843500DA7C62 /* ASInsetLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */; }; - ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED0C1B17843500DA7C62 /* ASLayout.mm */; }; - ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED0E1B17843500DA7C62 /* ASLayoutSpec.mm */; }; - ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED131B17843500DA7C62 /* ASOverlayLayoutSpec.mm */; }; - ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED151B17843500DA7C62 /* ASRatioLayoutSpec.mm */; }; - ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */; }; - ACF6ED321B17843500DA7C62 /* ASAbsoluteLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED191B17843500DA7C62 /* ASAbsoluteLayoutSpec.mm */; }; - ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */; }; - ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */; }; - ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */; }; ACF6ED5C1B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */; }; ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED541B178DC700DA7C62 /* ASDimensionTests.mm */; }; ACF6ED5E1B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */; }; @@ -343,12 +218,9 @@ ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */; }; ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */; }; AE6987C11DD04E1000B9E458 /* ASPagerNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AE6987C01DD04E1000B9E458 /* ASPagerNodeTests.m */; }; - AEB7B01B1C5962EA00662EF4 /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; }; - AEEC47E21C20C2DD00EC1693 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.m */; }; B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B30BF6531C5964B0004FCD53 /* ASLayoutManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */; }; B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */; }; B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -371,7 +243,7 @@ B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */; }; B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B35062091B010EFD0018CF92 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; + B35062091B010EFD0018CF92 /* ASScrollNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.mm */; }; B350620A1B010EFD0018CF92 /* ASTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */; settings = {ATTRIBUTES = (Public, ); }; }; B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3319ABD3E3004DAFF1 /* ASTableView.mm */; }; B350620C1B010EFD0018CF92 /* ASTableViewProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -387,16 +259,16 @@ B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; }; B35062171B010EFD0018CF92 /* ASDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521A1A3F83C40061C0BA /* ASDataController.mm */; }; - B350621B1B010EFD0018CF92 /* ASFlowLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */; }; + B350621B1B010EFD0018CF92 /* ASTableLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B350621C1B010EFD0018CF92 /* ASTableLayoutController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4640521C1A3F83C40061C0BA /* ASTableLayoutController.m */; }; B350621D1B010EFD0018CF92 /* ASHighlightOverlayLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */; }; B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521D1A3F83C40061C0BA /* ASLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521D1A3F83C40061C0BA /* ASLayoutController.h */; }; B35062211B010EFD0018CF92 /* ASLayoutRangeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */; }; - B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; }; B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */; }; B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062391B010EFD0018CF92 /* ASThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A12195D050800B7D73C /* ASThread.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -404,241 +276,122 @@ B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */; }; B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; }; B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F9195D050800B7D73C /* _ASAsyncTransaction.mm */; }; - B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */; }; + B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FB195D050800B7D73C /* _ASAsyncTransactionContainer.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */; }; B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */; }; B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */; }; - B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */; }; + B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */; settings = {ATTRIBUTES = (Private, ); }; }; B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */; }; - B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A05195D050800B7D73C /* _ASPendingState.h */; }; + B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A05195D050800B7D73C /* _ASPendingState.h */; settings = {ATTRIBUTES = (Private, ); }; }; B350624C1B010EFD0018CF92 /* _ASPendingState.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A06195D050800B7D73C /* _ASPendingState.mm */; }; - B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A07195D050800B7D73C /* _ASScopeTimer.h */; }; + B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A07195D050800B7D73C /* _ASScopeTimer.h */; settings = {ATTRIBUTES = (Private, ); }; }; B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */; }; - B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */; }; + B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */; settings = {ATTRIBUTES = (Private, ); }; }; B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */; }; B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */; }; - B35062521B010EFD0018CF92 /* ASDisplayNodeInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */; }; - B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */; }; + B35062521B010EFD0018CF92 /* ASDisplayNodeInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */; settings = {ATTRIBUTES = (Private, ); }; }; B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */; }; 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, ); }; }; B350625C1B010F070018CF92 /* ASLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3B1A15563400B4EBED /* ASLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; }; - B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; - B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; + B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC034A011E5FAF9700626263 /* ASElementMap.h in Headers */ = {isa = PBXBuildFile; fileRef = CC0349FF1E5FAF9700626263 /* ASElementMap.h */; }; + CC034A021E5FAF9700626263 /* ASElementMap.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A001E5FAF9700626263 /* ASElementMap.m */; }; + CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */; }; + CC034A0D1E60C3D500626263 /* ASRectTable.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A0B1E60C3D500626263 /* ASRectTable.h */; }; + CC034A0E1E60C3D500626263 /* ASRectTable.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A0C1E60C3D500626263 /* ASRectTable.m */; }; + CC034A101E60C9BF00626263 /* ASRectTableTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */; }; + CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */; }; + CC034A141E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m */; }; CC051F1F1D7A286A006434CB /* ASCALayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC051F1E1D7A286A006434CB /* ASCALayerTests.m */; }; CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */; }; + CC0F885B1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m */; }; + CC0F885C1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = CC0F885A1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Private, ); }; }; + CC0F885F1E4280B800576FED /* _ASCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0F885D1E4280B800576FED /* _ASCollectionViewCell.m */; }; + CC0F88601E4280B800576FED /* _ASCollectionViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */; settings = {ATTRIBUTES = (Private, ); }; }; + CC0F88621E4281E200576FED /* ASSectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC0F88631E4281E700576FED /* ASSupplementaryNodeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = CCE04B2B1E314A32006AEBBB /* ASSupplementaryNodeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC0F886C1E4286FA00576FED /* ReferenceImages_64 in Resources */ = {isa = PBXBuildFile; fileRef = CC0F88691E4286FA00576FED /* ReferenceImages_64 */; }; + CC0F886D1E4286FA00576FED /* ReferenceImages_iOS_10 in Resources */ = {isa = PBXBuildFile; fileRef = CC0F886A1E4286FA00576FED /* ReferenceImages_iOS_10 */; }; CC11F97A1DB181180024D77B /* ASNetworkImageNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */; }; - CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; - CC3B20851C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; + CC2F65EE1E5FFB1600DA57C9 /* ASMutableElementMap.h in Headers */ = {isa = PBXBuildFile; fileRef = CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */; }; + CC2F65EF1E5FFB1600DA57C9 /* ASMutableElementMap.m in Sources */ = {isa = PBXBuildFile; fileRef = CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.m */; }; + CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20871C3F7A5400798563 /* ASWeakSet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CC3B208B1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; }; CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; }; CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */; }; CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */; }; CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */; }; - CC4981BD1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */; }; - CC4C2A771D88E3BF0039ACAB /* ASTraceEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4C2A751D88E3BF0039ACAB /* ASTraceEvent.h */; }; - CC4C2A781D88E3BF0039ACAB /* ASTraceEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4C2A761D88E3BF0039ACAB /* ASTraceEvent.m */; }; + CC4C2A771D88E3BF0039ACAB /* ASTraceEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4C2A751D88E3BF0039ACAB /* ASTraceEvent.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC4C2A791D88E3BF0039ACAB /* ASTraceEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4C2A761D88E3BF0039ACAB /* ASTraceEvent.m */; }; - CC4C2A7A1D8902350039ACAB /* ASTraceEvent.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC4C2A751D88E3BF0039ACAB /* ASTraceEvent.h */; }; - CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = CC54A81B1D70077A00296A24 /* ASDispatch.h */; }; + CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = CC54A81B1D70077A00296A24 /* ASDispatch.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC54A81E1D7008B300296A24 /* ASDispatchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC54A81D1D7008B300296A24 /* ASDispatchTests.m */; }; - CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; }; + CC55A70D1E529FA200594372 /* UIResponder+AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.m in Sources */ = {isa = PBXBuildFile; fileRef = CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.m */; }; + CC55A7111E52A0F200594372 /* ASResponderChainEnumerator.h in Headers */ = {isa = PBXBuildFile; fileRef = CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */; }; + CC55A7121E52A0F200594372 /* ASResponderChainEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.m */; }; + CC57EAF71E3939350034C595 /* ASCollectionView+Undeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = CC2E317F1DAC353700EEE891 /* ASCollectionView+Undeprecated.h */; settings = {ATTRIBUTES = (Private, ); }; }; + CC57EAF81E3939450034C595 /* ASTableView+Undeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */; settings = {ATTRIBUTES = (Private, ); }; }; + CC58AA4B1E398E1D002C8CB4 /* ASBlockTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; }; CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = CC87BB941DA8193C0090E380 /* ASCellNode+Internal.h */; }; + CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = CC87BB941DA8193C0090E380 /* ASCellNode+Internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC8B05D61D73836400F54286 /* ASPerformanceTestContext.m in Sources */ = {isa = PBXBuildFile; fileRef = CC8B05D51D73836400F54286 /* ASPerformanceTestContext.m */; }; CC8B05D81D73979700F54286 /* ASTextNodePerformanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.m */; }; + CC90E1F41E383C0400FED591 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */; }; CCA221D31D6FA7EF00AF6A0F /* ASViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA221D21D6FA7EF00AF6A0F /* ASViewControllerTests.m */; }; CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */; }; - CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; }; - D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; - DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */; }; + CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */; }; - DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; }; - DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; + DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; settings = {ATTRIBUTES = (Private, ); }; }; DBC452DE1C5C6A6A00B16017 /* ArrayDiffingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */; }; DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */; }; DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DBDB83961C6E879900D0098C /* ASPagerFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */; }; 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 */; }; - DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; }; - DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; + DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; settings = {ATTRIBUTES = (Private, ); }; }; + DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; + DE7EF4F81DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = DE7EF4F71DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h */; settings = {ATTRIBUTES = (Private, ); }; }; 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 */; }; - DE89C1701DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.h in Headers */ = {isa = PBXBuildFile; fileRef = DE89C16A1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.h */; }; - DE89C1711DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16B1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m */; }; - DE89C1731DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16B1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m */; }; - DE89C1741DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DE89C16C1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h */; }; - DE89C1751DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16D1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m */; }; - DE89C1771DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16D1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m */; }; - DE89C1781DCEB9CC00D49D74 /* ASLayoutSpec+Debug.h in Headers */ = {isa = PBXBuildFile; fileRef = DE89C16E1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.h */; }; - DE89C1791DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16F1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m */; }; - DE89C17B1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16F1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m */; }; - DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; - DE8BEAC31C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; }; + DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; settings = {ATTRIBUTES = (Private, ); }; }; DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; }; DEB8ED7C1DD003D300DBDE55 /* ASLayoutTransition.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */; }; - DEB8ED7E1DD007F400DBDE55 /* ASLayoutElementInspectorNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DE89C16C1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h */; }; - DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; }; - DEC146B81C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */; }; + DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */; }; 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 /* ASLayoutTransition.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */; }; - E55D86321CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; }; + E516FC7F1E9FE24200714FF4 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = E516FC7D1E9FE24200714FF4 /* ASEqualityHashHelpers.h */; }; + E516FC801E9FE24200714FF4 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = E516FC7E1E9FE24200714FF4 /* ASEqualityHashHelpers.mm */; }; E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; }; - E5711A2C1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; }; - E5711A2E1C840C96009619D4 /* ASIndexedNodeContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */; }; - E5711A301C840C96009619D4 /* ASIndexedNodeContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */; }; + E5711A2C1C840C81009619D4 /* ASCollectionElement.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASCollectionElement.h */; settings = {ATTRIBUTES = (Private, ); }; }; + E5711A301C840C96009619D4 /* ASCollectionElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */; }; + E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */; }; + E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E58E9E451E941D74004CFC59 /* ASCollectionLayoutContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.mm */; }; + E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E58E9E491E941DA5004CFC59 /* ASCollectionLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */; settings = {ATTRIBUTES = (Private, ); }; }; + E58E9E4A1E941DA5004CFC59 /* ASCollectionLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */; }; + E5ABAC7B1E8564EE007AC15C /* ASRectTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E5ABAC791E8564EE007AC15C /* ASRectTable.h */; }; + E5ABAC7C1E8564EE007AC15C /* ASRectTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E5ABAC7A1E8564EE007AC15C /* ASRectTable.m */; }; + E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */ = {isa = PBXBuildFile; fileRef = E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */ = {isa = PBXBuildFile; fileRef = E5B077FE1E69F4EB00C24B5B /* ASElementMap.m */; }; + E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */; }; + E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */ = {isa = PBXBuildFile; fileRef = E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E5E281761E71C845006B67C2 /* ASCollectionLayoutState.m in Sources */ = {isa = PBXBuildFile; fileRef = E5E281751E71C845006B67C2 /* ASCollectionLayoutState.m */; }; F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */; }; - F7CE6C131D2CDB3E00BE4C15 /* ASPagerFlowLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */; }; - F7CE6C141D2CDB3E00BE4C15 /* ASMapNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; }; - F7CE6C151D2CDB3E00BE4C15 /* ASVideoNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; }; - F7CE6C161D2CDB3E00BE4C15 /* ASCellNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */; }; - F7CE6C171D2CDB3E00BE4C15 /* ASCollectionNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; }; - F7CE6C181D2CDB3E00BE4C15 /* ASCollectionNode+Beta.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; }; - F7CE6C191D2CDB3E00BE4C15 /* ASCollectionView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */; }; - F7CE6C1A1D2CDB3E00BE4C15 /* ASCollectionViewProtocols.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */; }; - F7CE6C1B1D2CDB3E00BE4C15 /* ASCollectionViewLayoutFacilitatorProtocol.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; }; - F7CE6C1C1D2CDB3E00BE4C15 /* ASControlNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09D5195D050800B7D73C /* ASControlNode.h */; }; - F7CE6C1D1D2CDB3E00BE4C15 /* ASButtonNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; }; - F7CE6C1E1D2CDB3E00BE4C15 /* ASControlNode+Subclasses.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */; }; - F7CE6C1F1D2CDB3E00BE4C15 /* ASDisplayNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09D8195D050800B7D73C /* ASDisplayNode.h */; }; - F7CE6C201D2CDB3E00BE4C15 /* ASDisplayNode+Beta.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; }; - F7CE6C211D2CDB3E00BE4C15 /* ASDisplayNode+Subclasses.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */; }; - F7CE6C221D2CDB3E00BE4C15 /* ASDisplayNodeExtras.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */; }; - F7CE6C231D2CDB3E00BE4C15 /* ASEditableTextNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */; }; - F7CE6C241D2CDB3E00BE4C15 /* ASImageNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09DD195D050800B7D73C /* ASImageNode.h */; }; - F7CE6C251D2CDB3E00BE4C15 /* UIImage+ASConvenience.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; }; - F7CE6C261D2CDB3E00BE4C15 /* ASMultiplexImageNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */; }; - F7CE6C271D2CDB3E00BE4C15 /* ASNavigationController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */; }; - F7CE6C281D2CDB3E00BE4C15 /* ASNetworkImageNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */; }; - F7CE6C291D2CDB3E00BE4C15 /* ASPagerNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; }; - F7CE6C2A1D2CDB3E00BE4C15 /* ASScrollNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; }; - F7CE6C2B1D2CDB3E00BE4C15 /* ASTabBarController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68FC85E01CE29B7E00EDD713 /* ASTabBarController.h */; }; - F7CE6C2C1D2CDB3E00BE4C15 /* ASTableNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; }; - F7CE6C2D1D2CDB3E00BE4C15 /* ASTableView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */; }; - F7CE6C2E1D2CDB3E00BE4C15 /* ASTableViewProtocols.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */; }; - F7CE6C2F1D2CDB3E00BE4C15 /* ASTextNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09DF195D050800B7D73C /* ASTextNode.h */; }; - F7CE6C301D2CDB3E00BE4C15 /* ASTextNode+Beta.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; }; - F7CE6C311D2CDB3E00BE4C15 /* ASViewController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; }; - F7CE6C321D2CDB3E00BE4C15 /* AsyncDisplayKit.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; }; - F7CE6C331D2CDB3E00BE4C15 /* AsyncDisplayKit+Debug.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */; }; - F7CE6C341D2CDB3E00BE4C15 /* ASContextTransitioning.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; }; - F7CE6C351D2CDB3E00BE4C15 /* ASVisibilityProtocols.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */; }; - F7CE6C361D2CDB3E00BE4C15 /* _ASDisplayLayer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */; }; - F7CE6C371D2CDB3E00BE4C15 /* _ASDisplayView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09E4195D050800B7D73C /* _ASDisplayView.h */; }; - F7CE6C381D2CDB3E00BE4C15 /* ASAbstractLayoutController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; }; - F7CE6C391D2CDB3E00BE4C15 /* ASBasicImageDownloader.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */; }; - F7CE6C3A1D2CDB3E00BE4C15 /* ASBatchContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 299DA1A71A828D2900162D41 /* ASBatchContext.h */; }; - F7CE6C3B1D2CDB3E00BE4C15 /* ASCollectionViewFlowLayoutInspector.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; }; - F7CE6C3D1D2CDB3E00BE4C15 /* ASCollectionViewLayoutController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; }; - F7CE6C3F1D2CDB3E00BE4C15 /* ASEnvironment.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 698548611CA9E025008A345F /* ASEnvironment.h */; }; - F7CE6C401D2CDB3E00BE4C15 /* ASFlowLayoutController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */; }; - F7CE6C411D2CDB3E00BE4C15 /* ASHighlightOverlayLayer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */; }; - F7CE6C421D2CDB3E00BE4C15 /* ASIndexPath.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; }; - F7CE6C431D2CDB3E00BE4C15 /* ASImageProtocols.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; }; - F7CE6C441D2CDB3E00BE4C15 /* ASImageContainerProtocolCategories.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */; }; - F7CE6C451D2CDB3E00BE4C15 /* ASPINRemoteImageDownloader.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */; }; - F7CE6C461D2CDB3E00BE4C15 /* ASLayoutController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4640521D1A3F83C40061C0BA /* ASLayoutController.h */; }; - F7CE6C471D2CDB3E00BE4C15 /* ASLayoutRangeType.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; }; - F7CE6C481D2CDB3E00BE4C15 /* ASMutableAttributedStringBuilder.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */; }; - F7CE6C491D2CDB3E00BE4C15 /* ASPhotosFrameworkImageRequest.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; }; - F7CE6C4A1D2CDB3E00BE4C15 /* ASRangeController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; }; - F7CE6C4B1D2CDB3E00BE4C15 /* ASRangeControllerUpdateRangeProtocol+Beta.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; }; - F7CE6C4C1D2CDB3E00BE4C15 /* ASRunLoopQueue.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; }; - F7CE6C4D1D2CDB3E00BE4C15 /* ASScrollDirection.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; }; - F7CE6C4E1D2CDB3E00BE4C15 /* ASThread.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A12195D050800B7D73C /* ASThread.h */; }; - F7CE6C4F1D2CDB3E00BE4C15 /* CoreGraphics+ASConvenience.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */; }; - F7CE6C501D2CDB3E00BE4C15 /* ASDataController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; }; - F7CE6C511D2CDB3E00BE4C15 /* ASChangeSetDataController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */; }; - F7CE6C521D2CDB3E00BE4C15 /* ASIndexedNodeContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; }; - F7CE6C531D2CDB3E00BE4C15 /* NSMutableAttributedString+TextKitAdditions.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */; }; - F7CE6C541D2CDB3E00BE4C15 /* _ASAsyncTransaction.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */; }; - F7CE6C551D2CDB3E00BE4C15 /* _ASAsyncTransactionContainer+Private.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */; }; - F7CE6C561D2CDB3E00BE4C15 /* _ASAsyncTransactionContainer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09FB195D050800B7D73C /* _ASAsyncTransactionContainer.h */; }; - F7CE6C571D2CDB3E00BE4C15 /* _ASAsyncTransactionGroup.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */; }; - F7CE6C581D2CDB3E00BE4C15 /* UICollectionViewLayout+ASConvenience.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */; }; - F7CE6C591D2CDB3E00BE4C15 /* UIView+ASConvenience.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */; }; - F7CE6C5A1D2CDB3E00BE4C15 /* ASTraitCollection.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */; }; - F7CE6C5B1D2CDB3E00BE4C15 /* ASAsciiArtBoxCreator.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; }; - F7CE6C5C1D2CDB3E00BE4C15 /* ASBackgroundLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */; }; - F7CE6C5D1D2CDB3E00BE4C15 /* ASCenterLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */; }; - F7CE6C5E1D2CDB3E00BE4C15 /* ASDimension.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED071B17843500DA7C62 /* ASDimension.h */; }; - F7CE6C5F1D2CDB3E00BE4C15 /* ASInsetLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */; }; - F7CE6C601D2CDB3E00BE4C15 /* ASLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED0B1B17843500DA7C62 /* ASLayout.h */; }; - F7CE6C611D2CDB3E00BE4C15 /* ASLayoutElement.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED111B17843500DA7C62 /* ASLayoutElement.h */; }; - F7CE6C621D2CDB3E00BE4C15 /* ASLayoutElementPrivate.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutElementPrivate.h */; }; - F7CE6C631D2CDB3E00BE4C15 /* ASLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED0D1B17843500DA7C62 /* ASLayoutSpec.h */; }; - F7CE6C641D2CDB3E00BE4C15 /* ASOverlayLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED121B17843500DA7C62 /* ASOverlayLayoutSpec.h */; }; - F7CE6C651D2CDB3E00BE4C15 /* ASRatioLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED141B17843500DA7C62 /* ASRatioLayoutSpec.h */; }; - F7CE6C661D2CDB3E00BE4C15 /* ASRelativeLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */; }; - F7CE6C681D2CDB3E00BE4C15 /* ASStackLayoutElement.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */; }; - F7CE6C691D2CDB3E00BE4C15 /* ASStackLayoutDefines.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */; }; - F7CE6C6A1D2CDB3E00BE4C15 /* ASStackLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */; }; - F7CE6C6B1D2CDB3E00BE4C15 /* ASAbsoluteLayoutElement.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h */; }; - F7CE6C6C1D2CDB3E00BE4C15 /* ASAbsoluteLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED181B17843500DA7C62 /* ASAbsoluteLayoutSpec.h */; }; - F7CE6C6D1D2CDB3E00BE4C15 /* ASTextKitComponents.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */; }; - F7CE6C6E1D2CDB3E00BE4C15 /* ASTextNodeWordKerner.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */; }; - F7CE6C6F1D2CDB3E00BE4C15 /* ASTextNodeTypes.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; }; - F7CE6C701D2CDB3E00BE4C15 /* ASTextKitAttributes.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */; }; - F7CE6C711D2CDB3F00BE4C15 /* ASTextKitContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754961BEE44CD00737CA5 /* ASTextKitContext.h */; }; - F7CE6C721D2CDB3F00BE4C15 /* ASTextKitEntityAttribute.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */; }; - F7CE6C731D2CDB3F00BE4C15 /* ASTextKitRenderer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */; }; - F7CE6C741D2CDB3F00BE4C15 /* ASTextKitRenderer+Positioning.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */; }; - F7CE6C751D2CDB3F00BE4C15 /* ASTextKitRenderer+TextChecking.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */; }; - F7CE6C761D2CDB3F00BE4C15 /* ASTextKitShadower.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; }; - F7CE6C771D2CDB3F00BE4C15 /* ASTextKitTailTruncater.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; }; - F7CE6C781D2CDB3F00BE4C15 /* ASTextKitFontSizeAdjuster.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; }; - F7CE6C791D2CDB3F00BE4C15 /* ASTextKitTruncating.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; }; - F7CE6C7B1D2CDB3F00BE4C15 /* ASAssert.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A43195D058D00B7D73C /* ASAssert.h */; }; - F7CE6C7C1D2CDB3F00BE4C15 /* ASAvailability.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0516FA3A1A15563400B4EBED /* ASAvailability.h */; }; - F7CE6C7D1D2CDB3F00BE4C15 /* ASBaseDefines.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A44195D058D00B7D73C /* ASBaseDefines.h */; }; - F7CE6C7E1D2CDB3F00BE4C15 /* ASEqualityHelpers.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; }; - F7CE6C7F1D2CDB3F00BE4C15 /* ASLog.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0516FA3B1A15563400B4EBED /* ASLog.h */; }; - F7CE6C801D2CDB5800BE4C15 /* ASVideoPlayerNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */; }; - F7CE6C811D2CDB5800BE4C15 /* ASCollectionInternal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; }; - F7CE6C821D2CDB5800BE4C15 /* ASTableViewInternal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */; }; - F7CE6C831D2CDB5800BE4C15 /* ASImageNode+tvOS.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */; }; - F7CE6C841D2CDB5800BE4C15 /* ASControlNode+tvOS.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */; }; - F7CE6C851D2CDB5800BE4C15 /* NSIndexSet+ASHelpers.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; }; - F7CE6C861D2CDB5800BE4C15 /* _ASDisplayViewAccessiblity.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; }; - F7CE6C871D2CDB5800BE4C15 /* ASDataController+Subclasses.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; }; - F7CE6C8B1D2CDB5800BE4C15 /* ASCollectionDataController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; }; - F7CE6C8C1D2CDB5800BE4C15 /* _ASCoreAnimationExtras.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */; }; - F7CE6C8D1D2CDB5800BE4C15 /* _ASHierarchyChangeSet.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */; }; - F7CE6C8F1D2CDB5800BE4C15 /* _ASScopeTimer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A07195D050800B7D73C /* _ASScopeTimer.h */; }; - F7CE6C901D2CDB5800BE4C15 /* _ASTransitionContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; }; - F7CE6C941D2CDB5800BE4C15 /* ASDisplayNodeInternal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */; }; - F7CE6C951D2CDB5800BE4C15 /* ASLayoutTransition.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; }; - F7CE6C961D2CDB5800BE4C15 /* ASEnvironmentInternal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */; }; - F7CE6C971D2CDB5800BE4C15 /* ASImageNode+CGExtras.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */; }; - F7CE6C981D2CDB5800BE4C15 /* ASInternalHelpers.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; }; - F7CE6C991D2CDB5800BE4C15 /* ASLayoutSpecUtilities.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */; }; - F7CE6C9A1D2CDB5800BE4C15 /* ASMultidimensionalArrayUtils.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; }; - F7CE6C9B1D2CDB5800BE4C15 /* ASPendingStateController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; - F7CE6C9D1D2CDB5800BE4C15 /* ASStackBaselinePositionedLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; - F7CE6C9E1D2CDB5800BE4C15 /* ASStackLayoutSpecUtilities.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */; }; - F7CE6C9F1D2CDB5800BE4C15 /* ASStackPositionedLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */; }; - F7CE6CA01D2CDB5800BE4C15 /* ASStackUnpositionedLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */; }; - F7CE6CA11D2CDB5800BE4C15 /* ASWeakSet.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC3B20871C3F7A5400798563 /* ASWeakSet.h */; }; - F7CE6CA21D2CDB5800BE4C15 /* ASDefaultPlaybackButton.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */; }; - F7CE6CA41D2CDB5800BE4C15 /* ASLayoutManager.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; }; - F7CE6CB71D2CE2D000BE4C15 /* ASLayoutElementExtensibility.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutElementExtensibility.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -649,182 +402,14 @@ remoteGlobalIDString = 057D02BE1AC0A66700C7AC3C; remoteInfo = AsyncDisplayKitTestHost; }; - 058D09C2195D04C000B7D73C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 058D09A4195D04C000B7D73C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 058D09AB195D04C000B7D73C; - remoteInfo = AsyncDisplayKit; - }; - DEACA2B11C425DC400FA9DDF /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 058D09A4195D04C000B7D73C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 058D09AB195D04C000B7D73C; - remoteInfo = AsyncDisplayKit; - }; /* End PBXContainerItemProxy section */ -/* Begin PBXCopyFilesBuildPhase section */ - 058D09AA195D04C000B7D73C /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "include/$(PRODUCT_NAME)"; - dstSubfolderSpec = 16; - files = ( - 68C2155C1DE11AA80019C4BC /* ASObjectDescriptionHelpers.h in CopyFiles */, - 68C2155B1DE11A790019C4BC /* ASCollectionViewLayoutInspector.h in CopyFiles */, - DEB8ED7E1DD007F400DBDE55 /* ASLayoutElementInspectorNode.h in CopyFiles */, - 69127CFE1DD2B387004BF6E2 /* ASEventLog.h in CopyFiles */, - 693117CE1DC7C72700DE4784 /* ASDisplayNode+Deprecated.h in CopyFiles */, - 69F381A51DA4630D00CF2278 /* NSArray+Diffing.h in CopyFiles */, - CC4C2A7A1D8902350039ACAB /* ASTraceEvent.h in CopyFiles */, - ACE87A331D73726300D7FF06 /* ASSectionContext.h in CopyFiles */, - F7CE6C981D2CDB5800BE4C15 /* ASInternalHelpers.h in CopyFiles */, - F7CE6CB71D2CE2D000BE4C15 /* ASLayoutElementExtensibility.h in CopyFiles */, - F7CE6C131D2CDB3E00BE4C15 /* ASPagerFlowLayout.h in CopyFiles */, - F7CE6C141D2CDB3E00BE4C15 /* ASMapNode.h in CopyFiles */, - F7CE6C151D2CDB3E00BE4C15 /* ASVideoNode.h in CopyFiles */, - F7CE6C161D2CDB3E00BE4C15 /* ASCellNode.h in CopyFiles */, - F7CE6C171D2CDB3E00BE4C15 /* ASCollectionNode.h in CopyFiles */, - F7CE6C181D2CDB3E00BE4C15 /* ASCollectionNode+Beta.h in CopyFiles */, - F7CE6C191D2CDB3E00BE4C15 /* ASCollectionView.h in CopyFiles */, - F7CE6C1A1D2CDB3E00BE4C15 /* ASCollectionViewProtocols.h in CopyFiles */, - F7CE6C1B1D2CDB3E00BE4C15 /* ASCollectionViewLayoutFacilitatorProtocol.h in CopyFiles */, - F7CE6C1C1D2CDB3E00BE4C15 /* ASControlNode.h in CopyFiles */, - F7CE6C1D1D2CDB3E00BE4C15 /* ASButtonNode.h in CopyFiles */, - F7CE6C1E1D2CDB3E00BE4C15 /* ASControlNode+Subclasses.h in CopyFiles */, - F7CE6C1F1D2CDB3E00BE4C15 /* ASDisplayNode.h in CopyFiles */, - F7CE6C201D2CDB3E00BE4C15 /* ASDisplayNode+Beta.h in CopyFiles */, - F7CE6C211D2CDB3E00BE4C15 /* ASDisplayNode+Subclasses.h in CopyFiles */, - F7CE6C221D2CDB3E00BE4C15 /* ASDisplayNodeExtras.h in CopyFiles */, - F7CE6C231D2CDB3E00BE4C15 /* ASEditableTextNode.h in CopyFiles */, - F7CE6C241D2CDB3E00BE4C15 /* ASImageNode.h in CopyFiles */, - F7CE6C251D2CDB3E00BE4C15 /* UIImage+ASConvenience.h in CopyFiles */, - F7CE6C261D2CDB3E00BE4C15 /* ASMultiplexImageNode.h in CopyFiles */, - F7CE6C271D2CDB3E00BE4C15 /* ASNavigationController.h in CopyFiles */, - F7CE6C281D2CDB3E00BE4C15 /* ASNetworkImageNode.h in CopyFiles */, - F7CE6C291D2CDB3E00BE4C15 /* ASPagerNode.h in CopyFiles */, - F7CE6C2A1D2CDB3E00BE4C15 /* ASScrollNode.h in CopyFiles */, - F7CE6C2B1D2CDB3E00BE4C15 /* ASTabBarController.h in CopyFiles */, - F7CE6C2C1D2CDB3E00BE4C15 /* ASTableNode.h in CopyFiles */, - F7CE6C2D1D2CDB3E00BE4C15 /* ASTableView.h in CopyFiles */, - F7CE6C2E1D2CDB3E00BE4C15 /* ASTableViewProtocols.h in CopyFiles */, - F7CE6C2F1D2CDB3E00BE4C15 /* ASTextNode.h in CopyFiles */, - F7CE6C301D2CDB3E00BE4C15 /* ASTextNode+Beta.h in CopyFiles */, - F7CE6C311D2CDB3E00BE4C15 /* ASViewController.h in CopyFiles */, - F7CE6C321D2CDB3E00BE4C15 /* AsyncDisplayKit.h in CopyFiles */, - F7CE6C331D2CDB3E00BE4C15 /* AsyncDisplayKit+Debug.h in CopyFiles */, - F7CE6C341D2CDB3E00BE4C15 /* ASContextTransitioning.h in CopyFiles */, - F7CE6C351D2CDB3E00BE4C15 /* ASVisibilityProtocols.h in CopyFiles */, - F7CE6C361D2CDB3E00BE4C15 /* _ASDisplayLayer.h in CopyFiles */, - F7CE6C371D2CDB3E00BE4C15 /* _ASDisplayView.h in CopyFiles */, - F7CE6C381D2CDB3E00BE4C15 /* ASAbstractLayoutController.h in CopyFiles */, - F7CE6C391D2CDB3E00BE4C15 /* ASBasicImageDownloader.h in CopyFiles */, - F7CE6C3A1D2CDB3E00BE4C15 /* ASBatchContext.h in CopyFiles */, - F7CE6C3B1D2CDB3E00BE4C15 /* ASCollectionViewFlowLayoutInspector.h in CopyFiles */, - F7CE6C3D1D2CDB3E00BE4C15 /* ASCollectionViewLayoutController.h in CopyFiles */, - F7CE6C3F1D2CDB3E00BE4C15 /* ASEnvironment.h in CopyFiles */, - F7CE6C401D2CDB3E00BE4C15 /* ASFlowLayoutController.h in CopyFiles */, - F7CE6C411D2CDB3E00BE4C15 /* ASHighlightOverlayLayer.h in CopyFiles */, - F7CE6C421D2CDB3E00BE4C15 /* ASIndexPath.h in CopyFiles */, - F7CE6C431D2CDB3E00BE4C15 /* ASImageProtocols.h in CopyFiles */, - F7CE6C441D2CDB3E00BE4C15 /* ASImageContainerProtocolCategories.h in CopyFiles */, - F7CE6C461D2CDB3E00BE4C15 /* ASLayoutController.h in CopyFiles */, - F7CE6C471D2CDB3E00BE4C15 /* ASLayoutRangeType.h in CopyFiles */, - F7CE6C481D2CDB3E00BE4C15 /* ASMutableAttributedStringBuilder.h in CopyFiles */, - F7CE6C491D2CDB3E00BE4C15 /* ASPhotosFrameworkImageRequest.h in CopyFiles */, - F7CE6C4A1D2CDB3E00BE4C15 /* ASRangeController.h in CopyFiles */, - F7CE6C4B1D2CDB3E00BE4C15 /* ASRangeControllerUpdateRangeProtocol+Beta.h in CopyFiles */, - F7CE6C4C1D2CDB3E00BE4C15 /* ASRunLoopQueue.h in CopyFiles */, - F7CE6C4D1D2CDB3E00BE4C15 /* ASScrollDirection.h in CopyFiles */, - F7CE6C4E1D2CDB3E00BE4C15 /* ASThread.h in CopyFiles */, - F7CE6C4F1D2CDB3E00BE4C15 /* CoreGraphics+ASConvenience.h in CopyFiles */, - F7CE6C501D2CDB3E00BE4C15 /* ASDataController.h in CopyFiles */, - F7CE6C511D2CDB3E00BE4C15 /* ASChangeSetDataController.h in CopyFiles */, - F7CE6C521D2CDB3E00BE4C15 /* ASIndexedNodeContext.h in CopyFiles */, - F7CE6C531D2CDB3E00BE4C15 /* NSMutableAttributedString+TextKitAdditions.h in CopyFiles */, - F7CE6C541D2CDB3E00BE4C15 /* _ASAsyncTransaction.h in CopyFiles */, - F7CE6C551D2CDB3E00BE4C15 /* _ASAsyncTransactionContainer+Private.h in CopyFiles */, - F7CE6C561D2CDB3E00BE4C15 /* _ASAsyncTransactionContainer.h in CopyFiles */, - F7CE6C571D2CDB3E00BE4C15 /* _ASAsyncTransactionGroup.h in CopyFiles */, - F7CE6C581D2CDB3E00BE4C15 /* UICollectionViewLayout+ASConvenience.h in CopyFiles */, - F7CE6C5B1D2CDB3E00BE4C15 /* ASAsciiArtBoxCreator.h in CopyFiles */, - F7CE6C5C1D2CDB3E00BE4C15 /* ASBackgroundLayoutSpec.h in CopyFiles */, - F7CE6C5D1D2CDB3E00BE4C15 /* ASCenterLayoutSpec.h in CopyFiles */, - F7CE6C5E1D2CDB3E00BE4C15 /* ASDimension.h in CopyFiles */, - F7CE6C5F1D2CDB3E00BE4C15 /* ASInsetLayoutSpec.h in CopyFiles */, - F7CE6C601D2CDB3E00BE4C15 /* ASLayout.h in CopyFiles */, - F7CE6C611D2CDB3E00BE4C15 /* ASLayoutElement.h in CopyFiles */, - F7CE6C621D2CDB3E00BE4C15 /* ASLayoutElementPrivate.h in CopyFiles */, - F7CE6C631D2CDB3E00BE4C15 /* ASLayoutSpec.h in CopyFiles */, - F7CE6C641D2CDB3E00BE4C15 /* ASOverlayLayoutSpec.h in CopyFiles */, - F7CE6C651D2CDB3E00BE4C15 /* ASRatioLayoutSpec.h in CopyFiles */, - F7CE6C661D2CDB3E00BE4C15 /* ASRelativeLayoutSpec.h in CopyFiles */, - F7CE6C681D2CDB3E00BE4C15 /* ASStackLayoutElement.h in CopyFiles */, - F7CE6C691D2CDB3E00BE4C15 /* ASStackLayoutDefines.h in CopyFiles */, - F7CE6C6A1D2CDB3E00BE4C15 /* ASStackLayoutSpec.h in CopyFiles */, - F7CE6C6B1D2CDB3E00BE4C15 /* ASAbsoluteLayoutElement.h in CopyFiles */, - F7CE6C6C1D2CDB3E00BE4C15 /* ASAbsoluteLayoutSpec.h in CopyFiles */, - F7CE6C6D1D2CDB3E00BE4C15 /* ASTextKitComponents.h in CopyFiles */, - F7CE6C6E1D2CDB3E00BE4C15 /* ASTextNodeWordKerner.h in CopyFiles */, - F7CE6C6F1D2CDB3E00BE4C15 /* ASTextNodeTypes.h in CopyFiles */, - F7CE6C701D2CDB3E00BE4C15 /* ASTextKitAttributes.h in CopyFiles */, - F7CE6C711D2CDB3F00BE4C15 /* ASTextKitContext.h in CopyFiles */, - F7CE6C721D2CDB3F00BE4C15 /* ASTextKitEntityAttribute.h in CopyFiles */, - F7CE6C731D2CDB3F00BE4C15 /* ASTextKitRenderer.h in CopyFiles */, - F7CE6C741D2CDB3F00BE4C15 /* ASTextKitRenderer+Positioning.h in CopyFiles */, - F7CE6C751D2CDB3F00BE4C15 /* ASTextKitRenderer+TextChecking.h in CopyFiles */, - F7CE6C761D2CDB3F00BE4C15 /* ASTextKitShadower.h in CopyFiles */, - F7CE6C771D2CDB3F00BE4C15 /* ASTextKitTailTruncater.h in CopyFiles */, - F7CE6C781D2CDB3F00BE4C15 /* ASTextKitFontSizeAdjuster.h in CopyFiles */, - F7CE6C791D2CDB3F00BE4C15 /* ASTextKitTruncating.h in CopyFiles */, - F7CE6C7B1D2CDB3F00BE4C15 /* ASAssert.h in CopyFiles */, - F7CE6C7C1D2CDB3F00BE4C15 /* ASAvailability.h in CopyFiles */, - F7CE6C7D1D2CDB3F00BE4C15 /* ASBaseDefines.h in CopyFiles */, - F7CE6C7E1D2CDB3F00BE4C15 /* ASEqualityHelpers.h in CopyFiles */, - F7CE6C7F1D2CDB3F00BE4C15 /* ASLog.h in CopyFiles */, - F7CE6C801D2CDB5800BE4C15 /* ASVideoPlayerNode.h in CopyFiles */, - F7CE6C811D2CDB5800BE4C15 /* ASCollectionInternal.h in CopyFiles */, - F7CE6C821D2CDB5800BE4C15 /* ASTableViewInternal.h in CopyFiles */, - F7CE6C831D2CDB5800BE4C15 /* ASImageNode+tvOS.h in CopyFiles */, - F7CE6C841D2CDB5800BE4C15 /* ASControlNode+tvOS.h in CopyFiles */, - F7CE6C851D2CDB5800BE4C15 /* NSIndexSet+ASHelpers.h in CopyFiles */, - F7CE6C861D2CDB5800BE4C15 /* _ASDisplayViewAccessiblity.h in CopyFiles */, - F7CE6C871D2CDB5800BE4C15 /* ASDataController+Subclasses.h in CopyFiles */, - F7CE6C451D2CDB3E00BE4C15 /* ASPINRemoteImageDownloader.h in CopyFiles */, - F7CE6C8B1D2CDB5800BE4C15 /* ASCollectionDataController.h in CopyFiles */, - F7CE6C591D2CDB3E00BE4C15 /* UIView+ASConvenience.h in CopyFiles */, - F7CE6C5A1D2CDB3E00BE4C15 /* ASTraitCollection.h in CopyFiles */, - F7CE6C8C1D2CDB5800BE4C15 /* _ASCoreAnimationExtras.h in CopyFiles */, - F7CE6C8D1D2CDB5800BE4C15 /* _ASHierarchyChangeSet.h in CopyFiles */, - F7CE6C8F1D2CDB5800BE4C15 /* _ASScopeTimer.h in CopyFiles */, - F7CE6C901D2CDB5800BE4C15 /* _ASTransitionContext.h in CopyFiles */, - F7CE6C941D2CDB5800BE4C15 /* ASDisplayNodeInternal.h in CopyFiles */, - F7CE6C951D2CDB5800BE4C15 /* ASLayoutTransition.h in CopyFiles */, - F7CE6C961D2CDB5800BE4C15 /* ASEnvironmentInternal.h in CopyFiles */, - F7CE6C971D2CDB5800BE4C15 /* ASImageNode+CGExtras.h in CopyFiles */, - F7CE6C991D2CDB5800BE4C15 /* ASLayoutSpecUtilities.h in CopyFiles */, - F7CE6C9A1D2CDB5800BE4C15 /* ASMultidimensionalArrayUtils.h in CopyFiles */, - F7CE6C9B1D2CDB5800BE4C15 /* ASPendingStateController.h in CopyFiles */, - F7CE6C9D1D2CDB5800BE4C15 /* ASStackBaselinePositionedLayout.h in CopyFiles */, - F7CE6C9E1D2CDB5800BE4C15 /* ASStackLayoutSpecUtilities.h in CopyFiles */, - F7CE6C9F1D2CDB5800BE4C15 /* ASStackPositionedLayout.h in CopyFiles */, - F7CE6CA01D2CDB5800BE4C15 /* ASStackUnpositionedLayout.h in CopyFiles */, - F7CE6CA11D2CDB5800BE4C15 /* ASWeakSet.h in CopyFiles */, - F7CE6CA21D2CDB5800BE4C15 /* ASDefaultPlaybackButton.h in CopyFiles */, - F7CE6CA41D2CDB5800BE4C15 /* ASLayoutManager.h in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - /* Begin PBXFileReference section */ - 044285011BAA3CC700D16268 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 044285011BAA3CC700D16268 /* AsyncDisplayKit.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = AsyncDisplayKit.modulemap; sourceTree = ""; }; 044285051BAA63FE00D16268 /* ASBatchFetching.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBatchFetching.h; sourceTree = ""; }; 044285061BAA63FE00D16268 /* ASBatchFetching.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBatchFetching.m; sourceTree = ""; }; - 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMultidimensionalArrayUtils.h; sourceTree = ""; }; - 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMultidimensionalArrayUtils.mm; sourceTree = ""; }; + 0442850B1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTwoDimensionalArrayUtils.h; sourceTree = ""; }; + 0442850C1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTwoDimensionalArrayUtils.m; sourceTree = ""; }; 0516FA3A1A15563400B4EBED /* ASAvailability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAvailability.h; sourceTree = ""; }; 0516FA3B1A15563400B4EBED /* ASLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLog.h; sourceTree = ""; }; 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMultiplexImageNode.h; sourceTree = ""; }; @@ -849,12 +434,10 @@ 057D02C21AC0A66700C7AC3C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 057D02C31AC0A66700C7AC3C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 057D02C51AC0A66700C7AC3C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 057D02C61AC0A66700C7AC3C /* AppDelegate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AppDelegate.mm; sourceTree = ""; }; + 057D02C61AC0A66700C7AC3C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AppDelegate.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEditableTextNode.h; sourceTree = ""; }; 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASEditableTextNode.mm; sourceTree = ""; }; - 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAsyncDisplayKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; 058D09AF195D04C000B7D73C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 058D09B3195D04C000B7D73C /* AsyncDisplayKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit-Prefix.pch"; sourceTree = ""; }; 058D09BC195D04C000B7D73C /* AsyncDisplayKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AsyncDisplayKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 058D09BD195D04C000B7D73C /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 058D09C0195D04C000B7D73C /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; @@ -873,7 +456,7 @@ 058D09DF195D050800B7D73C /* ASTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNode.h; sourceTree = ""; }; 058D09E0195D050800B7D73C /* ASTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTextNode.mm; sourceTree = ""; }; 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayLayer.h; sourceTree = ""; }; - 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayLayer.mm; sourceTree = ""; }; + 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = _ASDisplayLayer.mm; sourceTree = ""; }; 058D09E4195D050800B7D73C /* _ASDisplayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayView.h; sourceTree = ""; }; 058D09E5195D050800B7D73C /* _ASDisplayView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayView.mm; sourceTree = ""; }; 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASHighlightOverlayLayer.h; sourceTree = ""; }; @@ -883,15 +466,15 @@ 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableAttributedString+TextKitAdditions.h"; sourceTree = ""; }; 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableAttributedString+TextKitAdditions.m"; sourceTree = ""; }; 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASAsyncTransaction.h; sourceTree = ""; }; - 058D09F9195D050800B7D73C /* _ASAsyncTransaction.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASAsyncTransaction.mm; sourceTree = ""; }; + 058D09F9195D050800B7D73C /* _ASAsyncTransaction.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = _ASAsyncTransaction.mm; sourceTree = ""; }; 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "_ASAsyncTransactionContainer+Private.h"; sourceTree = ""; }; 058D09FB195D050800B7D73C /* _ASAsyncTransactionContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASAsyncTransactionContainer.h; sourceTree = ""; }; - 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASAsyncTransactionContainer.m; sourceTree = ""; }; + 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = _ASAsyncTransactionContainer.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASAsyncTransactionGroup.h; sourceTree = ""; }; - 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASAsyncTransactionGroup.m; sourceTree = ""; }; + 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = _ASAsyncTransactionGroup.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+ASConvenience.h"; sourceTree = ""; }; 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCoreAnimationExtras.h; sourceTree = ""; }; - 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASCoreAnimationExtras.mm; sourceTree = ""; }; + 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = _ASCoreAnimationExtras.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 058D0A05195D050800B7D73C /* _ASPendingState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASPendingState.h; sourceTree = ""; }; 058D0A06195D050800B7D73C /* _ASPendingState.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASPendingState.mm; sourceTree = ""; }; 058D0A07195D050800B7D73C /* _ASScopeTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASScopeTimer.h; sourceTree = ""; }; @@ -903,9 +486,9 @@ 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+CGExtras.h"; sourceTree = ""; }; 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASImageNode+CGExtras.m"; sourceTree = ""; }; 058D0A12195D050800B7D73C /* ASThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASThread.h; sourceTree = ""; }; - 058D0A2D195D057000B7D73C /* ASDisplayLayerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayLayerTests.m; sourceTree = ""; }; - 058D0A2E195D057000B7D73C /* ASDisplayNodeAppearanceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeAppearanceTests.m; sourceTree = ""; }; - 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeTests.m; sourceTree = ""; }; + 058D0A2D195D057000B7D73C /* ASDisplayLayerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASDisplayLayerTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 058D0A2E195D057000B7D73C /* ASDisplayNodeAppearanceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASDisplayNodeAppearanceTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeTests.mm; sourceTree = ""; }; 058D0A30195D057000B7D73C /* ASDisplayNodeTestsHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeTestsHelper.h; sourceTree = ""; }; 058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeTestsHelper.m; sourceTree = ""; }; 058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASMutableAttributedStringBuilderTests.m; sourceTree = ""; }; @@ -919,22 +502,16 @@ 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionNode.h; sourceTree = ""; }; 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionNode.mm; sourceTree = ""; }; 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHelpers.h; sourceTree = ""; }; - 204C979D1B362CB3002B1083 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionViewLayout+ASConvenience.h"; sourceTree = ""; }; 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionViewLayout+ASConvenience.m"; sourceTree = ""; }; 205F0E111B371BD7007741D0 /* ASScrollDirection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollDirection.m; sourceTree = ""; }; 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAbstractLayoutController.h; sourceTree = ""; }; 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASAbstractLayoutController.mm; sourceTree = ""; }; 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewLayoutController.h; sourceTree = ""; }; - 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionViewLayoutController.mm; sourceTree = ""; }; + 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewLayoutController.m; sourceTree = ""; }; 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CoreGraphics+ASConvenience.h"; sourceTree = ""; }; 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CoreGraphics+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 = ""; }; - 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 = ""; }; - 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDataController+Subclasses.h"; sourceTree = ""; }; 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionViewFlowLayoutInspectorTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTruncationTests.mm; sourceTree = ""; }; 254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTests.mm; sourceTree = ""; }; @@ -955,7 +532,7 @@ 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTailTruncater.h; path = TextKit/ASTextKitTailTruncater.h; sourceTree = ""; }; 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 = ""; }; - 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitComponents.m; path = TextKit/ASTextKitComponents.m; sourceTree = ""; }; + 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitComponents.mm; path = TextKit/ASTextKitComponents.mm; 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 /* ASTextKitComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitComponents.h; path = TextKit/ASTextKitComponents.h; sourceTree = ""; }; @@ -967,18 +544,18 @@ 2911485B1A77147A005D0878 /* ASControlNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNodeTests.m; sourceTree = ""; }; 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutRangeType.h; sourceTree = ""; }; 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASBasicImageDownloaderInternal.h; sourceTree = ""; }; - 296A0A311A951715005ACEAA /* ASScrollDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASScrollDirection.h; path = AsyncDisplayKit/Details/ASScrollDirection.h; sourceTree = SOURCE_ROOT; }; - 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBatchFetchingTests.m; sourceTree = ""; }; + 296A0A311A951715005ACEAA /* ASScrollDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASScrollDirection.h; path = Source/Details/ASScrollDirection.h; sourceTree = SOURCE_ROOT; }; + 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASBatchFetchingTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 299DA1A71A828D2900162D41 /* ASBatchContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBatchContext.h; sourceTree = ""; }; 299DA1A81A828D2900162D41 /* ASBatchContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBatchContext.mm; sourceTree = ""; }; - 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderContextTests.m; sourceTree = ""; }; - 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; 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 = ""; }; + 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASBasicImageDownloaderContextTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionReusableView.h; sourceTree = ""; }; + 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASCollectionReusableView.m; sourceTree = ""; }; + 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTableViewTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 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 = ""; }; + 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableLayoutController.h; sourceTree = ""; }; + 4640521C1A3F83C40061C0BA /* ASTableLayoutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableLayoutController.m; sourceTree = ""; }; 4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = ""; }; 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Deprecated.h"; sourceTree = ""; }; 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASImageNode+AnimatedImage.mm"; sourceTree = ""; }; @@ -1000,37 +577,50 @@ 68FC85E11CE29B7E00EDD713 /* ASTabBarController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTabBarController.m; sourceTree = ""; }; 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASVisibilityProtocols.h; sourceTree = ""; }; 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASVisibilityProtocols.m; sourceTree = ""; }; + 6900C5F31E8072DA00BCD75C /* ASImageNode+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+Private.h"; sourceTree = ""; }; 6907C2561DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASObjectDescriptionHelpers.h; sourceTree = ""; }; 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASObjectDescriptionHelpers.m; sourceTree = ""; }; - 69527B111DC84292004785FB /* ASLayoutElementStylePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutElementStylePrivate.h; path = AsyncDisplayKit/Layout/ASLayoutElementStylePrivate.h; sourceTree = SOURCE_ROOT; }; + 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDimensionInternal.mm; sourceTree = ""; }; + 690C35631E055C7B00069B91 /* ASDimensionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDimensionInternal.h; sourceTree = ""; }; + 690C35651E0567C600069B91 /* ASDimensionDeprecated.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDimensionDeprecated.mm; sourceTree = ""; }; + 690C356A1E05680300069B91 /* ASDimensionDeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDimensionDeprecated.h; sourceTree = ""; }; + 690ED58D1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementStylePrivate.h; sourceTree = ""; }; + 690ED5921E36D118000627C0 /* ASControlNode+tvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+tvOS.h"; sourceTree = ""; }; + 690ED5931E36D118000627C0 /* ASControlNode+tvOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASControlNode+tvOS.m"; sourceTree = ""; }; + 690ED5941E36D118000627C0 /* ASImageNode+tvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+tvOS.h"; sourceTree = ""; }; + 690ED5951E36D118000627C0 /* ASImageNode+tvOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASImageNode+tvOS.m"; sourceTree = ""; }; + 692510131E74FB44003F2DD0 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; + 692BE8D61E36B65B00C86D87 /* ASLayoutSpecPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecPrivate.h; sourceTree = ""; }; + 6947B0BC1E36B4E30007C478 /* ASStackUnpositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackUnpositionedLayout.h; sourceTree = ""; }; + 6947B0BD1E36B4E30007C478 /* ASStackUnpositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackUnpositionedLayout.mm; sourceTree = ""; }; + 6947B0C11E36B5040007C478 /* ASStackPositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackPositionedLayout.h; sourceTree = ""; }; + 6947B0C21E36B5040007C478 /* ASStackPositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackPositionedLayout.mm; sourceTree = ""; }; 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeLayout.mm; sourceTree = ""; }; 6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeLayout.h; sourceTree = ""; }; 695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASWrapperSpecSnapshotTests.mm; sourceTree = ""; }; - 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASEventLog.h; path = AsyncDisplayKit/Details/ASEventLog.h; sourceTree = SOURCE_ROOT; }; - 696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASEventLog.mm; path = AsyncDisplayKit/Details/ASEventLog.mm; sourceTree = SOURCE_ROOT; }; - 696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBackgroundLayoutSpecSnapshotTests.mm; sourceTree = ""; }; - 69708BA41D76386D005C3CF9 /* ASEqualityHashHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASEqualityHashHelpers.h; path = TextKit/ASEqualityHashHelpers.h; sourceTree = ""; }; - 69708BA51D76386D005C3CF9 /* ASEqualityHashHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASEqualityHashHelpers.mm; path = TextKit/ASEqualityHashHelpers.mm; sourceTree = ""; }; - 6977965D1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ASLayoutSpec+Subclasses.h"; path = "AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.h"; sourceTree = ""; }; - 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "ASLayoutSpec+Subclasses.mm"; path = "AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.mm"; sourceTree = ""; }; + 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEventLog.h; sourceTree = ""; }; + 696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEventLog.mm; sourceTree = ""; }; + 696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASBackgroundLayoutSpecSnapshotTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 6977965D1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASLayoutSpec+Subclasses.h"; sourceTree = ""; }; + 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASLayoutSpec+Subclasses.mm"; sourceTree = ""; }; 697B31591CFE4B410049936F /* ASEditableTextNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASEditableTextNodeTests.m; sourceTree = ""; }; - 698548611CA9E025008A345F /* ASEnvironment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEnvironment.h; sourceTree = ""; }; - 698C8B601CAB49FC0052DC3F /* ASLayoutElementExtensibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutElementExtensibility.h; path = AsyncDisplayKit/Layout/ASLayoutElementExtensibility.h; sourceTree = ""; }; + 698371D91E4379CD00437585 /* ASNodeController+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASNodeController+Beta.h"; sourceTree = ""; }; + 698371DA1E4379CD00437585 /* ASNodeController+Beta.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASNodeController+Beta.m"; sourceTree = ""; }; + 698C8B601CAB49FC0052DC3F /* ASLayoutElementExtensibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementExtensibility.h; sourceTree = ""; }; + 698DFF431E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutSpecUtilities.h; sourceTree = ""; }; + 698DFF461E36B7E9002891F1 /* ASLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecUtilities.h; sourceTree = ""; }; + 699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutSpecTests.m; sourceTree = ""; }; 69B225661D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeLayoutTests.mm; sourceTree = ""; }; 69B225681D7265DA00B25B22 /* ASXCTExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASXCTExtensions.h; sourceTree = ""; }; - 69C4CAF51DA3147000B1EC9B /* ASLayoutElementStylePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutElementStylePrivate.h; path = ../Layout/ASLayoutElementStylePrivate.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 = ""; }; - 69EEA0A01D9AB43900B46420 /* ASLayoutSpecPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecPrivate.h; sourceTree = ""; }; 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = ""; }; 69FEE53C1D95A9AF0086F066 /* ASLayoutElementStyleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutElementStyleTests.m; 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; name = "AsyncDisplayKit+Debug.h"; path = "../AsyncDisplayKit+Debug.h"; sourceTree = ""; }; - 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "AsyncDisplayKit+Debug.m"; 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 = ""; }; + 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; path = ASRelativeLayoutSpec.mm; sourceTree = ""; }; + 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRelativeLayoutSpec.h; sourceTree = ""; }; 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = ""; }; 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ASConvenience.h"; sourceTree = ""; }; 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ASConvenience.m"; sourceTree = ""; }; @@ -1044,24 +634,18 @@ 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDefaultPlaybackButton.m; sourceTree = ""; }; 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASVideoPlayerNode.h; sourceTree = ""; }; 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASVideoPlayerNode.mm; sourceTree = ""; }; - 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+tvOS.h"; sourceTree = ""; }; - 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASImageNode+tvOS.m"; sourceTree = ""; }; - 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+tvOS.h"; sourceTree = ""; }; - 92074A661CC8BADA00918F75 /* ASControlNode+tvOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASControlNode+tvOS.m"; sourceTree = ""; }; + 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+Yoga.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 /* ASStackLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutElement.h; path = AsyncDisplayKit/Layout/ASStackLayoutElement.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 = ""; }; - 9C6BB3B01B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASAbsoluteLayoutElement.h; path = AsyncDisplayKit/Layout/ASAbsoluteLayoutElement.h; sourceTree = ""; }; + 9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutElement.h; sourceTree = ""; }; + 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAsciiArtBoxCreator.h; sourceTree = ""; }; + 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASAsciiArtBoxCreator.m; sourceTree = ""; }; + 9C6BB3B01B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAbsoluteLayoutElement.h; sourceTree = ""; }; 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTraitCollection.h; sourceTree = ""; }; 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTraitCollection.m; 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 = ""; }; 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitFontSizeAdjuster.mm; path = TextKit/ASTextKitFontSizeAdjuster.mm; sourceTree = ""; }; - 9CDC18CB1B910E12004965E2 /* ASLayoutElementPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutElementPrivate.h; path = AsyncDisplayKit/Layout/ASLayoutElementPrivate.h; sourceTree = ""; }; - 9CFFC6BD1CCAC52B006A6476 /* ASEnvironment.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEnvironment.mm; sourceTree = ""; }; + 9CDC18CB1B910E12004965E2 /* ASLayoutElementPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementPrivate.h; sourceTree = ""; }; 9CFFC6BF1CCAC73C006A6476 /* ASViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASViewController.mm; sourceTree = ""; }; 9CFFC6C11CCAC768006A6476 /* ASTableNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTableNode.mm; sourceTree = ""; }; 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionViewTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; @@ -1071,12 +655,10 @@ A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASPINRemoteImageDownloader.m; path = Details/ASPINRemoteImageDownloader.m; sourceTree = ""; }; A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitFontSizeAdjuster.h; path = TextKit/ASTextKitFontSizeAdjuster.h; sourceTree = ""; }; A373200E1C571B050011FC94 /* ASTextNode+Beta.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASTextNode+Beta.h"; sourceTree = ""; }; - AC026B571BD3F61800BBC17E /* ASAbsoluteLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASAbsoluteLayoutSpecSnapshotTests.m; sourceTree = ""; }; - AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASChangeSetDataController.h; sourceTree = ""; }; - AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASChangeSetDataController.mm; sourceTree = ""; }; + AC026B571BD3F61800BBC17E /* ASAbsoluteLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASAbsoluteLayoutSpecSnapshotTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASHierarchyChangeSet.h; sourceTree = ""; }; AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASHierarchyChangeSet.mm; sourceTree = ""; }; - AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutDefines.h; path = AsyncDisplayKit/Layout/ASStackLayoutDefines.h; sourceTree = ""; }; + AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutDefines.h; sourceTree = ""; }; AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionView.h; sourceTree = ""; }; AC3C4A501A1139C100143C57 /* ASCollectionView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionView.mm; sourceTree = ""; }; AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewProtocols.h; sourceTree = ""; }; @@ -1086,37 +668,31 @@ AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableViewInternal.h; sourceTree = ""; }; ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASViewController.h; sourceTree = ""; }; ACE87A2B1D73696800D7FF06 /* ASSectionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASSectionContext.h; path = Details/ASSectionContext.h; sourceTree = ""; }; - ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASBackgroundLayoutSpec.h; path = AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h; sourceTree = ""; }; - ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASBackgroundLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm; sourceTree = ""; }; - ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASCenterLayoutSpec.h; path = AsyncDisplayKit/Layout/ASCenterLayoutSpec.h; sourceTree = ""; }; - ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASCenterLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm; sourceTree = ""; }; - ACF6ED071B17843500DA7C62 /* ASDimension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDimension.h; path = AsyncDisplayKit/Layout/ASDimension.h; sourceTree = ""; }; - ACF6ED081B17843500DA7C62 /* ASDimension.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASDimension.mm; path = AsyncDisplayKit/Layout/ASDimension.mm; sourceTree = ""; }; - ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASInsetLayoutSpec.h; path = AsyncDisplayKit/Layout/ASInsetLayoutSpec.h; sourceTree = ""; }; - ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASInsetLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm; sourceTree = ""; }; - ACF6ED0B1B17843500DA7C62 /* ASLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayout.h; path = AsyncDisplayKit/Layout/ASLayout.h; sourceTree = ""; }; - ACF6ED0C1B17843500DA7C62 /* ASLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASLayout.mm; path = AsyncDisplayKit/Layout/ASLayout.mm; sourceTree = ""; }; - ACF6ED0D1B17843500DA7C62 /* ASLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutSpec.h; path = AsyncDisplayKit/Layout/ASLayoutSpec.h; sourceTree = ""; }; - ACF6ED0E1B17843500DA7C62 /* ASLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASLayoutSpec.mm; sourceTree = ""; }; - ACF6ED111B17843500DA7C62 /* ASLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutElement.h; path = AsyncDisplayKit/Layout/ASLayoutElement.h; sourceTree = ""; }; - ACF6ED121B17843500DA7C62 /* ASOverlayLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASOverlayLayoutSpec.h; path = AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h; sourceTree = ""; }; - ACF6ED131B17843500DA7C62 /* ASOverlayLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASOverlayLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm; sourceTree = ""; }; - ACF6ED141B17843500DA7C62 /* ASRatioLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRatioLayoutSpec.h; path = AsyncDisplayKit/Layout/ASRatioLayoutSpec.h; sourceTree = ""; }; - ACF6ED151B17843500DA7C62 /* ASRatioLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASRatioLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm; sourceTree = ""; }; - ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutSpec.h; path = AsyncDisplayKit/Layout/ASStackLayoutSpec.h; sourceTree = ""; }; - ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASStackLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASStackLayoutSpec.mm; sourceTree = ""; }; - ACF6ED181B17843500DA7C62 /* ASAbsoluteLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASAbsoluteLayoutSpec.h; path = AsyncDisplayKit/Layout/ASAbsoluteLayoutSpec.h; sourceTree = ""; }; - ACF6ED191B17843500DA7C62 /* ASAbsoluteLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASAbsoluteLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASAbsoluteLayoutSpec.mm; sourceTree = ""; }; + ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBackgroundLayoutSpec.h; sourceTree = ""; }; + ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASBackgroundLayoutSpec.mm; sourceTree = ""; }; + ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCenterLayoutSpec.h; sourceTree = ""; }; + ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCenterLayoutSpec.mm; sourceTree = ""; }; + ACF6ED071B17843500DA7C62 /* ASDimension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDimension.h; sourceTree = ""; }; + ACF6ED081B17843500DA7C62 /* ASDimension.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDimension.mm; sourceTree = ""; }; + ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASInsetLayoutSpec.h; sourceTree = ""; }; + ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASInsetLayoutSpec.mm; sourceTree = ""; }; + ACF6ED0B1B17843500DA7C62 /* ASLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayout.h; sourceTree = ""; }; + ACF6ED0C1B17843500DA7C62 /* ASLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayout.mm; sourceTree = ""; }; + ACF6ED0D1B17843500DA7C62 /* ASLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpec.h; sourceTree = ""; }; + ACF6ED0E1B17843500DA7C62 /* ASLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASLayoutSpec.mm; sourceTree = ""; }; + ACF6ED111B17843500DA7C62 /* ASLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElement.h; sourceTree = ""; }; + ACF6ED121B17843500DA7C62 /* ASOverlayLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASOverlayLayoutSpec.h; sourceTree = ""; }; + ACF6ED131B17843500DA7C62 /* ASOverlayLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASOverlayLayoutSpec.mm; sourceTree = ""; }; + ACF6ED141B17843500DA7C62 /* ASRatioLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRatioLayoutSpec.h; sourceTree = ""; }; + ACF6ED151B17843500DA7C62 /* ASRatioLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASRatioLayoutSpec.mm; sourceTree = ""; }; + ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutSpec.h; sourceTree = ""; }; + ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackLayoutSpec.mm; sourceTree = ""; }; + ACF6ED181B17843500DA7C62 /* ASAbsoluteLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAbsoluteLayoutSpec.h; sourceTree = ""; }; + ACF6ED191B17843500DA7C62 /* ASAbsoluteLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASAbsoluteLayoutSpec.mm; sourceTree = ""; }; ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASInternalHelpers.h; sourceTree = ""; }; ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASInternalHelpers.m; sourceTree = ""; }; - ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecUtilities.h; sourceTree = ""; }; - ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutSpecUtilities.h; sourceTree = ""; }; - ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackPositionedLayout.h; sourceTree = ""; }; - ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackPositionedLayout.mm; sourceTree = ""; }; - ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackUnpositionedLayout.h; sourceTree = ""; }; - ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASStackUnpositionedLayout.mm; sourceTree = ""; }; - 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 = ""; }; + ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCenterLayoutSpecSnapshotTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + ACF6ED541B178DC700DA7C62 /* ASDimensionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDimensionTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASInsetLayoutSpecSnapshotTests.mm; sourceTree = ""; }; ACF6ED571B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecSnapshotTestsHelper.h; sourceTree = ""; }; ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASLayoutSpecSnapshotTestsHelper.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; @@ -1135,18 +711,34 @@ B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutManager.h; path = TextKit/ASLayoutManager.h; sourceTree = ""; }; B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASLayoutManager.m; path = TextKit/ASLayoutManager.m; sourceTree = ""; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - B35061DD1B010EDF0018CF92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "../AsyncDisplayKit-iOS/Info.plist"; sourceTree = ""; }; BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.profile.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.profile.xcconfig"; sourceTree = ""; }; + CC0349FF1E5FAF9700626263 /* ASElementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASElementMap.h; sourceTree = ""; }; + CC034A001E5FAF9700626263 /* ASElementMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASElementMap.m; sourceTree = ""; }; + CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Convenience.h"; sourceTree = ""; }; + CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASDisplayNode+Convenience.m"; sourceTree = ""; }; + CC034A0B1E60C3D500626263 /* ASRectTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRectTable.h; sourceTree = ""; }; + CC034A0C1E60C3D500626263 /* ASRectTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRectTable.m; sourceTree = ""; }; + CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRectTableTests.m; sourceTree = ""; }; + CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit+IGListKitMethods.h"; sourceTree = ""; }; + CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AsyncDisplayKit+IGListKitMethods.m"; sourceTree = ""; }; CC051F1E1D7A286A006434CB /* ASCALayerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCALayerTests.m; sourceTree = ""; }; CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASUICollectionViewTests.m; sourceTree = ""; }; + CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; + CC0F885A1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = ""; }; + CC0F885D1E4280B800576FED /* _ASCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASCollectionViewCell.m; sourceTree = ""; }; + CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionViewCell.h; sourceTree = ""; }; + CC0F88691E4286FA00576FED /* ReferenceImages_64 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ReferenceImages_64; sourceTree = ""; }; + CC0F886A1E4286FA00576FED /* ReferenceImages_iOS_10 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ReferenceImages_iOS_10; sourceTree = ""; }; CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASNetworkImageNodeTests.m; sourceTree = ""; }; CC2E317F1DAC353700EEE891 /* ASCollectionView+Undeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCollectionView+Undeprecated.h"; sourceTree = ""; }; + CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMutableElementMap.h; sourceTree = ""; }; + CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASMutableElementMap.m; sourceTree = ""; }; CC3B20811C3F76D600798563 /* ASPendingStateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPendingStateController.h; sourceTree = ""; }; CC3B20821C3F76D600798563 /* ASPendingStateController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPendingStateController.mm; sourceTree = ""; }; CC3B20871C3F7A5400798563 /* ASWeakSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakSet.h; sourceTree = ""; }; CC3B20881C3F7A5400798563 /* ASWeakSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakSet.m; sourceTree = ""; }; CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakSetTests.m; sourceTree = ""; }; - CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBridgedPropertiesTests.mm; sourceTree = ""; }; + CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASBridgedPropertiesTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewThrashTests.m; sourceTree = ""; }; CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexSet+ASHelpers.h"; sourceTree = ""; }; CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexSet+ASHelpers.m"; sourceTree = ""; }; @@ -1154,46 +746,69 @@ CC4C2A761D88E3BF0039ACAB /* ASTraceEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTraceEvent.m; sourceTree = ""; }; CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASTableView+Undeprecated.h"; sourceTree = ""; }; CC54A81B1D70077A00296A24 /* ASDispatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASDispatch.h; sourceTree = ""; }; - CC54A81D1D7008B300296A24 /* ASDispatchTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDispatchTests.m; sourceTree = ""; }; + CC54A81D1D7008B300296A24 /* ASDispatchTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASDispatchTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIResponder+AsyncDisplayKit.h"; sourceTree = ""; }; + CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIResponder+AsyncDisplayKit.m"; sourceTree = ""; }; + CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASResponderChainEnumerator.h; sourceTree = ""; }; + CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASResponderChainEnumerator.m; sourceTree = ""; }; + CC57EAF91E394EA40034C595 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBlockTypes.h; sourceTree = ""; }; CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = ""; }; CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = ""; }; CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = ""; }; - CC87BB941DA8193C0090E380 /* ASCellNode+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ASCellNode+Internal.h"; path = "AsyncDisplayKit/ASCellNode+Internal.h"; sourceTree = SOURCE_ROOT; }; + CC87BB941DA8193C0090E380 /* ASCellNode+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCellNode+Internal.h"; sourceTree = ""; }; CC8B05D41D73836400F54286 /* ASPerformanceTestContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPerformanceTestContext.h; sourceTree = ""; }; CC8B05D51D73836400F54286 /* ASPerformanceTestContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPerformanceTestContext.m; sourceTree = ""; }; CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodePerformanceTests.m; sourceTree = ""; }; CCA221D21D6FA7EF00AF6A0F /* ASViewControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASViewControllerTests.m; sourceTree = ""; }; CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeSnapshotTests.m; sourceTree = ""; }; + CCBD05DE1E4147B000D18509 /* ASIGListAdapterBasedDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIGListAdapterBasedDataSource.m; sourceTree = ""; }; + CCBD05DF1E4147B000D18509 /* ASIGListAdapterBasedDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIGListAdapterBasedDataSource.h; sourceTree = ""; }; + CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSectionController.h; sourceTree = ""; }; + CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IGListAdapter+AsyncDisplayKit.h"; sourceTree = ""; }; + CCE04B211E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "IGListAdapter+AsyncDisplayKit.m"; sourceTree = ""; }; + CCE04B2B1E314A32006AEBBB /* ASSupplementaryNodeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSupplementaryNodeSource.h; sourceTree = ""; }; D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; - D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; + D785F6611A74327E00291744 /* ASScrollNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASScrollNode.mm; sourceTree = ""; }; DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = _ASTransitionContext.h; path = ../_ASTransitionContext.h; sourceTree = ""; }; DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = _ASTransitionContext.m; path = ../_ASTransitionContext.m; sourceTree = ""; }; DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASContextTransitioning.h; sourceTree = ""; }; DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Diffing.h"; sourceTree = ""; }; DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Diffing.m"; sourceTree = ""; }; - DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayDiffingTests.m; sourceTree = ""; }; - DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeImplicitHierarchyTests.m; sourceTree = ""; }; + DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ArrayDiffingTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASDisplayNodeImplicitHierarchyTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPagerFlowLayout.h; sourceTree = ""; }; DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPagerFlowLayout.m; sourceTree = ""; }; DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; - DE89C16A1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementInspectorCell.h; sourceTree = ""; }; - DE89C16B1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutElementInspectorCell.m; sourceTree = ""; }; - DE89C16C1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementInspectorNode.h; sourceTree = ""; }; - DE89C16D1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutElementInspectorNode.m; sourceTree = ""; }; - DE89C16E1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASLayoutSpec+Debug.h"; sourceTree = ""; }; - DE89C16F1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASLayoutSpec+Debug.m"; sourceTree = ""; }; + DE7EF4F71DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkSubclasses.h"; sourceTree = ""; }; DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASCollectionInternal.h; path = Details/ASCollectionInternal.h; sourceTree = ""; }; 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 = ""; }; + E516FC7D1E9FE24200714FF4 /* ASEqualityHashHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHashHelpers.h; sourceTree = ""; }; + E516FC7E1E9FE24200714FF4 /* ASEqualityHashHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEqualityHashHelpers.mm; sourceTree = ""; }; E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutTransition.mm; sourceTree = ""; }; E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTransition.h; sourceTree = ""; }; - E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASLayoutElement.mm; path = AsyncDisplayKit/Layout/ASLayoutElement.mm; sourceTree = ""; }; - E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexedNodeContext.h; sourceTree = ""; }; - E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASIndexedNodeContext.mm; sourceTree = ""; }; + E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutElement.mm; sourceTree = ""; }; + E5711A2A1C840C81009619D4 /* ASCollectionElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionElement.h; sourceTree = ""; }; + E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionElement.mm; sourceTree = ""; }; + E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionFlowLayoutDelegate.h; sourceTree = ""; }; + E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionFlowLayoutDelegate.m; sourceTree = ""; }; + E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutContext.h; sourceTree = ""; }; + E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayoutContext.mm; sourceTree = ""; }; + E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutDelegate.h; sourceTree = ""; }; + E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayout.h; sourceTree = ""; }; + E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayout.mm; sourceTree = ""; }; + E5ABAC791E8564EE007AC15C /* ASRectTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRectTable.h; sourceTree = ""; }; + E5ABAC7A1E8564EE007AC15C /* ASRectTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRectTable.m; sourceTree = ""; }; + E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASElementMap.h; sourceTree = ""; }; + E5B077FE1E69F4EB00C24B5B /* ASElementMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASElementMap.m; sourceTree = ""; }; + E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCollectionLayoutContext+Private.h"; sourceTree = ""; }; + E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutState.h; sourceTree = ""; }; + E5E281751E71C845006B67C2 /* ASCollectionLayoutState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionLayoutState.m; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeExtrasTests.m; sourceTree = ""; }; 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 = ""; }; @@ -1204,18 +819,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 058D09A9195D04C000B7D73C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 92DD2FE91BF4D4870074C9DD /* MapKit.framework in Frameworks */, - 051943151A1575670030A7D0 /* Photos.framework in Frameworks */, - 051943131A1575630030A7D0 /* AssetsLibrary.framework in Frameworks */, - 058D09B0195D04C000B7D73C /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1223,12 +826,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 92DD2FEA1BF4D49B0074C9DD /* MapKit.framework in Frameworks */, - 0515EA221A1576A100BA8B9A /* AssetsLibrary.framework in Frameworks */, - 0515EA211A15769900BA8B9A /* Photos.framework in Frameworks */, + CC90E1F41E383C0400FED591 /* AsyncDisplayKit.framework in Frameworks */, 058D09BE195D04C000B7D73C /* XCTest.framework in Frameworks */, 058D09C1195D04C000B7D73C /* UIKit.framework in Frameworks */, - 058D09C4195D04C000B7D73C /* libAsyncDisplayKit.a in Frameworks */, 058D09BF195D04C000B7D73C /* Foundation.framework in Frameworks */, DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */, ); @@ -1239,7 +839,6 @@ buildActionMask = 2147483647; files = ( 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */, - B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */, B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */, B350625D1B0111740018CF92 /* Photos.framework in Frameworks */, ); @@ -1251,18 +850,18 @@ 057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */ = { isa = PBXGroup; children = ( - 204C979D1B362CB3002B1083 /* Default-568h@2x.png */, 057D02C51AC0A66700C7AC3C /* AppDelegate.h */, - 057D02C61AC0A66700C7AC3C /* AppDelegate.mm */, + 057D02C61AC0A66700C7AC3C /* AppDelegate.m */, 057D02C11AC0A66700C7AC3C /* Supporting Files */, ); name = AsyncDisplayKitTestHost; - path = ../AsyncDisplayKitTestHost; + path = TestHost; sourceTree = ""; }; 057D02C11AC0A66700C7AC3C /* Supporting Files */ = { isa = PBXGroup; children = ( + 692510131E74FB44003F2DD0 /* Default-568h@2x.png */, 057D02C21AC0A66700C7AC3C /* Info.plist */, 057D02C31AC0A66700C7AC3C /* main.m */, ); @@ -1272,9 +871,8 @@ 058D09A3195D04C000B7D73C = { isa = PBXGroup; children = ( - 058D09B1195D04C000B7D73C /* AsyncDisplayKit */, - 058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */, - B35061DB1B010EDF0018CF92 /* AsyncDisplayKit-iOS */, + 058D09B1195D04C000B7D73C /* Source */, + 058D09C5195D04C000B7D73C /* Tests */, 058D09AE195D04C000B7D73C /* Frameworks */, 058D09AD195D04C000B7D73C /* Products */, FD40E2760492F0CAAEAD552D /* Pods */, @@ -1287,7 +885,6 @@ 058D09AD195D04C000B7D73C /* Products */ = { isa = PBXGroup; children = ( - 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */, 058D09BC195D04C000B7D73C /* AsyncDisplayKitTests.xctest */, 057D02BF1AC0A66700C7AC3C /* AsyncDisplayKitTestHost.app */, B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */, @@ -1309,9 +906,11 @@ name = Frameworks; sourceTree = ""; }; - 058D09B1195D04C000B7D73C /* AsyncDisplayKit */ = { + 058D09B1195D04C000B7D73C /* Source */ = { isa = PBXGroup; children = ( + CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */, + CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */, DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */, DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */, 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */, @@ -1339,8 +938,11 @@ 058D09D8195D050800B7D73C /* ASDisplayNode.h */, 058D09D9195D050800B7D73C /* ASDisplayNode.mm */, 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */, + 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */, 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */, 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */, + CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */, + CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */, 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */, 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */, 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */, @@ -1356,13 +958,15 @@ 68FC85DD1CE29AB700EDD713 /* ASNavigationController.m */, 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */, 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */, + 698371D91E4379CD00437585 /* ASNodeController+Beta.h */, + 698371DA1E4379CD00437585 /* ASNodeController+Beta.m */, 25E327541C16819500A2170C /* ASPagerNode.h */, 25E327551C16819500A2170C /* ASPagerNode.m */, A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */, A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */, ACE87A2B1D73696800D7FF06 /* ASSectionContext.h */, D785F6601A74327E00291744 /* ASScrollNode.h */, - D785F6611A74327E00291744 /* ASScrollNode.m */, + D785F6611A74327E00291744 /* ASScrollNode.mm */, 68FC85E01CE29B7E00EDD713 /* ASTabBarController.h */, 68FC85E11CE29B7E00EDD713 /* ASTabBarController.m */, B0F880581BEAEC7500D17647 /* ASTableNode.h */, @@ -1380,30 +984,33 @@ DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */, 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */, 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */, - 92074A5E1CC8B9DD00918F75 /* tvOS */, + CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */, + CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.m */, + 058D0A42195D058D00B7D73C /* Base */, DE89C1691DCEB9CC00D49D74 /* Debug */, 058D09E1195D050800B7D73C /* Details */, 058D0A01195D050800B7D73C /* Private */, AC6456051B0A333200CF11B8 /* Layout */, 257754661BED245B00737CA5 /* TextKit */, + 690ED5911E36D118000627C0 /* tvOS */, 058D09B2195D04C000B7D73C /* Supporting Files */, ); - path = AsyncDisplayKit; + path = Source; sourceTree = ""; }; 058D09B2195D04C000B7D73C /* Supporting Files */ = { isa = PBXGroup; children = ( - 058D0A42195D058D00B7D73C /* Base */, - 058D09B3195D04C000B7D73C /* AsyncDisplayKit-Prefix.pch */, - 044285011BAA3CC700D16268 /* module.modulemap */, + CC57EAF91E394EA40034C595 /* Info.plist */, + 044285011BAA3CC700D16268 /* AsyncDisplayKit.modulemap */, ); name = "Supporting Files"; sourceTree = ""; }; - 058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */ = { + 058D09C5195D04C000B7D73C /* Tests */ = { isa = PBXGroup; children = ( + CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */, CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */, CC051F1E1D7A286A006434CB /* ASCALayerTests.m */, CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.m */, @@ -1444,14 +1051,14 @@ ACF6ED541B178DC700DA7C62 /* ASDimensionTests.mm */, 058D0A2D195D057000B7D73C /* ASDisplayLayerTests.m */, 058D0A2E195D057000B7D73C /* ASDisplayNodeAppearanceTests.m */, - 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.m */, + 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.mm */, 69B225661D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm */, 058D0A30195D057000B7D73C /* ASDisplayNodeTestsHelper.h */, 058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.m */, 697B31591CFE4B410049936F /* ASEditableTextNodeTests.m */, 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */, 058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */, - 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */, + 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */, CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */, 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */, 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */, @@ -1465,13 +1072,16 @@ 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */, 69FEE53C1D95A9AF0086F066 /* ASLayoutElementStyleTests.m */, 695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */, + 699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */, ); - path = AsyncDisplayKitTests; + path = Tests; sourceTree = ""; }; 058D09C6195D04C000B7D73C /* Supporting Files */ = { isa = PBXGroup; children = ( + CC0F88691E4286FA00576FED /* ReferenceImages_64 */, + CC0F886A1E4286FA00576FED /* ReferenceImages_iOS_10 */, 058D09C7195D04C000B7D73C /* AsyncDisplayKitTests-Info.plist */, 058D09C8195D04C000B7D73C /* InfoPlist.strings */, ); @@ -1481,6 +1091,10 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( + CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */, + CC0F885D1E4280B800576FED /* _ASCollectionViewCell.m */, + 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */, + 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */, 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */, 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */, 058D09E4195D050800B7D73C /* _ASDisplayView.h */, @@ -1495,21 +1109,17 @@ 299DA1A81A828D2900162D41 /* ASBatchContext.mm */, 68C215561DE10D330019C4BC /* ASCollectionViewLayoutInspector.h */, 68C215571DE10D330019C4BC /* ASCollectionViewLayoutInspector.m */, - 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */, - 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */, 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */, - 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */, - 698548611CA9E025008A345F /* ASEnvironment.h */, - 9CFFC6BD1CCAC52B006A6476 /* ASEnvironment.mm */, - 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */, - 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */, + 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */, + 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */, + 696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */, + 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */, + 4640521C1A3F83C40061C0BA /* ASTableLayoutController.m */, 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */, 058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */, 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */, 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m */, 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */, - 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */, - 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */, 4640521D1A3F83C40061C0BA /* ASLayoutController.h */, 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */, 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */, @@ -1541,6 +1151,7 @@ 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */, 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */, 25B171EA1C12242700508A7A /* Data Controller */, + E5B077EB1E6843AF00C24B5B /* Collection Layout */, DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */, @@ -1572,6 +1183,14 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( + CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */, + CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.m */, + E5ABAC791E8564EE007AC15C /* ASRectTable.h */, + E5ABAC7A1E8564EE007AC15C /* ASRectTable.m */, + CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */, + CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.m */, + 6947B0BB1E36B4E30007C478 /* Layout */, + CCE04B2A1E313EDA006AEBBB /* Collection Data Adapter */, 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */, 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */, AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */, @@ -1585,10 +1204,14 @@ 044285051BAA63FE00D16268 /* ASBatchFetching.h */, 044285061BAA63FE00D16268 /* ASBatchFetching.m */, CC87BB941DA8193C0090E380 /* ASCellNode+Internal.h */, + E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */, + E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */, + E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */, CC2E317F1DAC353700EEE891 /* ASCollectionView+Undeprecated.h */, + CC0F885A1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h */, + CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m */, 9F98C0231DBDF2A300476D92 /* ASControlTargetAction.h */, 9F98C0241DBDF2A300476D92 /* ASControlTargetAction.m */, - 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */, 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */, 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */, AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */, @@ -1598,36 +1221,25 @@ 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */, 058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */, DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */, + DE7EF4F71DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h */, 058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */, 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */, 6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */, 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */, - 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */, - 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */, - 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */, - 696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */, + E516FC7D1E9FE24200714FF4 /* ASEqualityHashHelpers.h */, + E516FC7E1E9FE24200714FF4 /* ASEqualityHashHelpers.mm */, + 6900C5F31E8072DA00BCD75C /* ASImageNode+Private.h */, 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */, 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */, 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */, ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */, ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */, - 69C4CAF51DA3147000B1EC9B /* ASLayoutElementStylePrivate.h */, - 69527B111DC84292004785FB /* ASLayoutElementStylePrivate.h */, - 69EEA0A01D9AB43900B46420 /* ASLayoutSpecPrivate.h */, - ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */, E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */, E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */, - 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */, - 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */, + 0442850B1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h */, + 0442850C1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m */, CC3B20811C3F76D600798563 /* ASPendingStateController.h */, CC3B20821C3F76D600798563 /* ASPendingStateController.mm */, - 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */, - 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */, - ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */, - ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */, - ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */, - ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */, - ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */, CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */, 83A7D9581D44542100BF333E /* ASWeakMap.h */, 83A7D9591D44542100BF333E /* ASWeakMap.m */, @@ -1645,17 +1257,15 @@ 0516FA3B1A15563400B4EBED /* ASLog.h */, ); path = Base; - sourceTree = SOURCE_ROOT; + sourceTree = ""; }; 257754661BED245B00737CA5 /* TextKit */ = { isa = PBXGroup; children = ( - 69708BA41D76386D005C3CF9 /* ASEqualityHashHelpers.h */, - 69708BA51D76386D005C3CF9 /* ASEqualityHashHelpers.mm */, B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */, B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */, 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */, - 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */, + 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */, 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */, 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */, 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */, @@ -1689,29 +1299,42 @@ children = ( DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */, DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */, - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */, - 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */, 464052191A3F83C40061C0BA /* ASDataController.h */, 4640521A1A3F83C40061C0BA /* ASDataController.mm */, - AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */, - AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */, - E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */, - E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */, + E5711A2A1C840C81009619D4 /* ASCollectionElement.h */, + E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */, + E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */, + E5B077FE1E69F4EB00C24B5B /* ASElementMap.m */, AC6145401D8AFAE8003D62A2 /* ASSection.h */, AC6145421D8AFD4F003D62A2 /* ASSection.m */, ); name = "Data Controller"; sourceTree = ""; }; - 92074A5E1CC8B9DD00918F75 /* tvOS */ = { + 690ED5911E36D118000627C0 /* tvOS */ = { isa = PBXGroup; children = ( - 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */, - 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */, - 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */, - 92074A661CC8BADA00918F75 /* ASControlNode+tvOS.m */, + 690ED5921E36D118000627C0 /* ASControlNode+tvOS.h */, + 690ED5931E36D118000627C0 /* ASControlNode+tvOS.m */, + 690ED5941E36D118000627C0 /* ASImageNode+tvOS.h */, + 690ED5951E36D118000627C0 /* ASImageNode+tvOS.m */, ); - name = tvOS; + path = tvOS; + sourceTree = ""; + }; + 6947B0BB1E36B4E30007C478 /* Layout */ = { + isa = PBXGroup; + children = ( + 690ED58D1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h */, + 692BE8D61E36B65B00C86D87 /* ASLayoutSpecPrivate.h */, + 698DFF461E36B7E9002891F1 /* ASLayoutSpecUtilities.h */, + 698DFF431E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h */, + 6947B0C11E36B5040007C478 /* ASStackPositionedLayout.h */, + 6947B0C21E36B5040007C478 /* ASStackPositionedLayout.mm */, + 6947B0BC1E36B4E30007C478 /* ASStackUnpositionedLayout.h */, + 6947B0BD1E36B4E30007C478 /* ASStackUnpositionedLayout.mm */, + ); + path = Layout; sourceTree = ""; }; AC6456051B0A333200CF11B8 /* Layout */ = { @@ -1728,6 +1351,10 @@ ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */, ACF6ED071B17843500DA7C62 /* ASDimension.h */, ACF6ED081B17843500DA7C62 /* ASDimension.mm */, + 690C356A1E05680300069B91 /* ASDimensionDeprecated.h */, + 690C35651E0567C600069B91 /* ASDimensionDeprecated.mm */, + 690C35631E055C7B00069B91 /* ASDimensionInternal.h */, + 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */, ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */, ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */, ACF6ED0B1B17843500DA7C62 /* ASLayout.h */, @@ -1751,42 +1378,62 @@ ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */, ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */, ); - name = Layout; - path = ..; + path = Layout; sourceTree = ""; }; - B35061DB1B010EDF0018CF92 /* AsyncDisplayKit-iOS */ = { + CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */ = { isa = PBXGroup; children = ( - B35061DC1B010EDF0018CF92 /* Supporting Files */, + CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */, + CCE04B2B1E314A32006AEBBB /* ASSupplementaryNodeSource.h */, + CCF92DCE1E315FC50019E9C6 /* IGListKit Support */, ); - name = "AsyncDisplayKit-iOS"; - path = AsyncDisplayKit; + name = "Collection Data Adapter"; sourceTree = ""; }; - B35061DC1B010EDF0018CF92 /* Supporting Files */ = { + CCE04B2A1E313EDA006AEBBB /* Collection Data Adapter */ = { isa = PBXGroup; children = ( - B35061DD1B010EDF0018CF92 /* Info.plist */, + CCBD05DF1E4147B000D18509 /* ASIGListAdapterBasedDataSource.h */, + CCBD05DE1E4147B000D18509 /* ASIGListAdapterBasedDataSource.m */, ); - name = "Supporting Files"; + name = "Collection Data Adapter"; + sourceTree = ""; + }; + CCF92DCE1E315FC50019E9C6 /* IGListKit Support */ = { + isa = PBXGroup; + children = ( + CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */, + CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m */, + CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */, + CCE04B211E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.m */, + ); + name = "IGListKit Support"; sourceTree = ""; }; DE89C1691DCEB9CC00D49D74 /* Debug */ = { isa = PBXGroup; children = ( - DE89C16A1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.h */, - DE89C16B1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m */, - DE89C16C1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h */, - DE89C16D1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m */, - DE89C16E1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.h */, - DE89C16F1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m */, 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */, 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */, ); path = Debug; sourceTree = ""; }; + E5B077EB1E6843AF00C24B5B /* Collection Layout */ = { + isa = PBXGroup; + children = ( + E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */, + E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.mm */, + E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */, + E5E281751E71C845006B67C2 /* ASCollectionLayoutState.m */, + E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */, + E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */, + E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */, + ); + name = "Collection Layout"; + sourceTree = ""; + }; FD40E2760492F0CAAEAD552D /* Pods */ = { isa = PBXGroup; children = ( @@ -1804,163 +1451,176 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */, + E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */, + E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */, + E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */, + E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */, 696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */, + 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */, + 3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */, + 690C356B1E05680300069B91 /* ASDimensionDeprecated.h in Headers */, 683489281D70DE3400327501 /* ASDisplayNode+Deprecated.h in Headers */, + 698371DB1E4379CD00437585 /* ASNodeController+Beta.h in Headers */, 6907C2581DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h in Headers */, 69E0E8A71D356C9400627613 /* ASEqualityHelpers.h in Headers */, 698C8B621CAB49FC0052DC3F /* ASLayoutElementExtensibility.h in Headers */, - 698548641CA9E025008A345F /* ASEnvironment.h in Headers */, - AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */, 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */, B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */, - 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */, 68355B411CB57A6C001D4E68 /* ASImageContainerProtocolCategories.h in Headers */, 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */, B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */, - DE89C1741DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h in Headers */, B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */, - 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, 68C215581DE10D330019C4BC /* ASCollectionViewLayoutInspector.h in Headers */, B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */, - B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */, B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */, B35062111B010EFD0018CF92 /* _ASDisplayView.h in Headers */, - 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, - B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */, - CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */, 9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */, - B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */, - 254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */, 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */, 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 */, + CC0F88621E4281E200576FED /* ASSectionController.h in Headers */, A2763D7A1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.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 */, - 69EEA0A11D9AB43900B46420 /* ASLayoutSpecPrivate.h in Headers */, B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */, B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */, - B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */, B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */, - 92074A681CC8BADA00918F75 /* ASControlNode+tvOS.h in Headers */, - 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */, - AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */, B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */, - CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */, 34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */, - 9C70F20C1CDBE9B6007D6C76 /* ASCollectionDataController.h in Headers */, + CC55A7111E52A0F200594372 /* ASResponderChainEnumerator.h in Headers */, 18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */, - 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */, B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */, ACE87A2C1D73696800D7FF06 /* ASSectionContext.h in Headers */, - 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */, 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */, - CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */, - 92074A621CC8BA1900918F75 /* ASImageNode+tvOS.h in Headers */, B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */, 68FC85E31CE29B7E00EDD713 /* ASTabBarController.h in Headers */, - DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, - 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */, B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */, B35061F81B010EFD0018CF92 /* ASControlNode.h in Headers */, B35062171B010EFD0018CF92 /* ASDataController.h in Headers */, 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */, 68FC85EA1CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */, - 69C4CAF61DA3147000B1EC9B /* ASLayoutElementStylePrivate.h in Headers */, A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */, - DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */, 9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */, 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */, - B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */, B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */, B35061FB1B010EFD0018CF92 /* ASDisplayNode.h in Headers */, B35061FE1B010EFD0018CF92 /* ASDisplayNodeExtras.h in Headers */, - B35062521B010EFD0018CF92 /* ASDisplayNodeInternal.h in Headers */, + CC0F88601E4280B800576FED /* _ASCollectionViewCell.h in Headers */, B35062001B010EFD0018CF92 /* ASEditableTextNode.h in Headers */, 680346941CE4052A0009FEB4 /* ASNavigationController.h in Headers */, - B350621B1B010EFD0018CF92 /* ASFlowLayoutController.h in Headers */, + B350621B1B010EFD0018CF92 /* ASTableLayoutController.h in Headers */, B350621D1B010EFD0018CF92 /* ASHighlightOverlayLayer.h in Headers */, C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */, - AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, - B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */, - 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */, - 6977965F1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h in Headers */, 7AB338671C55B3460055FDE8 /* ASRelativeLayoutSpec.h in Headers */, B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */, B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */, - 430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */, - 9C70F20B1CDBE9A4007D6C76 /* ASDataController+Subclasses.h in Headers */, 34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */, - 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */, 34EFC7671B701CD900AD841F /* ASLayout.h in Headers */, - DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */, 34EFC7691B701CE100AD841F /* ASLayoutElement.h in Headers */, - 9CDC18CD1B910E12004965E2 /* ASLayoutElementPrivate.h in Headers */, + 698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */, + 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */, + DE7EF4F81DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h in Headers */, + CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */, + 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, + B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */, + 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, + B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */, + CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */, + B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */, + CC0F88631E4281E700576FED /* ASSupplementaryNodeSource.h in Headers */, + 254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */, + 254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */, + 690ED58E1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h in Headers */, + CC55A70D1E529FA200594372 /* UIResponder+AsyncDisplayKit.h in Headers */, + 254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */, + 254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */, + 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */, + 254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */, + 68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */, + B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */, + 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */, + AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */, + CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */, + 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */, + 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */, + CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */, + DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, + E516FC7F1E9FE24200714FF4 /* ASEqualityHashHelpers.h in Headers */, + 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */, + 6947B0C31E36B5040007C478 /* ASStackPositionedLayout.h in Headers */, + DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */, + B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */, + CC57EAF71E3939350034C595 /* ASCollectionView+Undeprecated.h in Headers */, + B35062521B010EFD0018CF92 /* ASDisplayNodeInternal.h in Headers */, + AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, + B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */, + E58E9E491E941DA5004CFC59 /* ASCollectionLayout.h in Headers */, + 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */, + CC58AA4B1E398E1D002C8CB4 /* ASBlockTypes.h in Headers */, + 6977965F1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h in Headers */, + 692BE8D71E36B65B00C86D87 /* ASLayoutSpecPrivate.h in Headers */, + 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */, + E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */, + DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */, 9F98C0271DBE29FC00476D92 /* ASControlTargetAction.h in Headers */, + CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */, + 690ED5961E36D118000627C0 /* ASControlNode+tvOS.h in Headers */, + 695943401D70815300B0EE1F /* ASDisplayNodeLayout.h in Headers */, + 0442850E1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h in Headers */, + DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */, + B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */, + AC6145411D8AFAE8003D62A2 /* ASSection.h in Headers */, + 8BBBAB8C1CEBAF1700107FC6 /* ASDefaultPlaybackButton.h in Headers */, + 690ED5991E36D118000627C0 /* ASImageNode+tvOS.h in Headers */, + 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */, + 698DFF441E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h in Headers */, + CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */, + 83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */, + E5711A2C1C840C81009619D4 /* ASCollectionElement.h in Headers */, + 6947B0BE1E36B4E30007C478 /* ASStackUnpositionedLayout.h in Headers */, + CC4C2A771D88E3BF0039ACAB /* ASTraceEvent.h in Headers */, + 254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */, + DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */, + CC57EAF81E3939450034C595 /* ASTableView+Undeprecated.h in Headers */, + 254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */, + 9CDC18CD1B910E12004965E2 /* ASLayoutElementPrivate.h in Headers */, B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */, B35062211B010EFD0018CF92 /* ASLayoutRangeType.h in Headers */, + CC2F65EE1E5FFB1600DA57C9 /* ASMutableElementMap.h in Headers */, 34EFC76A1B701CE600AD841F /* ASLayoutSpec.h in Headers */, - 695943401D70815300B0EE1F /* ASDisplayNodeLayout.h in Headers */, - 34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */, B350625C1B010F070018CF92 /* ASLog.h in Headers */, - 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */, - 69527B121DC84292004785FB /* ASLayoutElementStylePrivate.h in Headers */, CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */, - DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */, B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */, - B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */, - AC6145411D8AFAE8003D62A2 /* ASSection.h in Headers */, DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */, B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */, B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */, - 8BBBAB8C1CEBAF1700107FC6 /* ASDefaultPlaybackButton.h in Headers */, B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */, 34EFC76C1B701CED00AD841F /* ASOverlayLayoutSpec.h in Headers */, + E5ABAC7B1E8564EE007AC15C /* ASRectTable.h in Headers */, B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */, 34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */, - 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */, - DE89C1781DCEB9CC00D49D74 /* ASLayoutSpec+Debug.h in Headers */, DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, + 6900C5F41E8072DA00BCD75C /* ASImageNode+Private.h in Headers */, 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */, - CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */, - 83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */, - 69708BA61D76386D005C3CF9 /* ASEqualityHashHelpers.h in Headers */, B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */, 254C6B751BF94DF4003EC431 /* ASTextKitComponents.h in Headers */, B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, 25E327571C16819500A2170C /* ASPagerNode.h in Headers */, - 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutElement.h in Headers */, - DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, + CC0F885C1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h in Headers */, 764D83D51C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h in Headers */, - E5711A2C1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */, - CC4C2A771D88E3BF0039ACAB /* ASTraceEvent.h in Headers */, - 254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */, CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, - DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.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 */, - DE89C1701DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.h in Headers */, - 34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */, 9C6BB3B31B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h in Headers */, 34EFC7731B701D0700AD841F /* ASAbsoluteLayoutSpec.h in Headers */, - 254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */, B350620A1B010EFD0018CF92 /* ASTableView.h in Headers */, B350620C1B010EFD0018CF92 /* ASTableViewProtocols.h in Headers */, B350620D1B010EFD0018CF92 /* ASTextNode.h in Headers */, @@ -1988,30 +1648,12 @@ buildRules = ( ); dependencies = ( - DEACA2B21C425DC400FA9DDF /* PBXTargetDependency */, ); name = AsyncDisplayKitTestHost; productName = AsyncDisplayKitTestHost; productReference = 057D02BF1AC0A66700C7AC3C /* AsyncDisplayKitTestHost.app */; productType = "com.apple.product-type.application"; }; - 058D09AB195D04C000B7D73C /* AsyncDisplayKit */ = { - isa = PBXNativeTarget; - buildConfigurationList = 058D09CF195D04C000B7D73C /* Build configuration list for PBXNativeTarget "AsyncDisplayKit" */; - buildPhases = ( - 058D09A8195D04C000B7D73C /* Sources */, - 058D09A9195D04C000B7D73C /* Frameworks */, - 058D09AA195D04C000B7D73C /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = AsyncDisplayKit; - productName = AsyncDisplayKit; - productReference = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; - productType = "com.apple.product-type.library.static"; - }; 058D09BB195D04C000B7D73C /* AsyncDisplayKitTests */ = { isa = PBXNativeTarget; buildConfigurationList = 058D09D2195D04C000B7D73C /* Build configuration list for PBXNativeTarget "AsyncDisplayKitTests" */; @@ -2026,7 +1668,6 @@ buildRules = ( ); dependencies = ( - 058D09C3195D04C000B7D73C /* PBXTargetDependency */, 057D02E61AC0A67000C7AC3C /* PBXTargetDependency */, ); name = AsyncDisplayKitTests; @@ -2034,9 +1675,9 @@ productReference = 058D09BC195D04C000B7D73C /* AsyncDisplayKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - B35061D91B010EDF0018CF92 /* AsyncDisplayKit-iOS */ = { + B35061D91B010EDF0018CF92 /* AsyncDisplayKit */ = { isa = PBXNativeTarget; - buildConfigurationList = B35061ED1B010EDF0018CF92 /* Build configuration list for PBXNativeTarget "AsyncDisplayKit-iOS" */; + buildConfigurationList = B35061ED1B010EDF0018CF92 /* Build configuration list for PBXNativeTarget "AsyncDisplayKit" */; buildPhases = ( B35061D51B010EDF0018CF92 /* Sources */, B35061D61B010EDF0018CF92 /* Frameworks */, @@ -2047,7 +1688,7 @@ ); dependencies = ( ); - name = "AsyncDisplayKit-iOS"; + name = AsyncDisplayKit; productName = AsyncDisplayKit; productReference = B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */; productType = "com.apple.product-type.framework"; @@ -2059,7 +1700,7 @@ isa = PBXProject; attributes = { CLASSPREFIX = AS; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0820; ORGANIZATIONNAME = Facebook; TargetAttributes = { 057D02BE1AC0A66700C7AC3C = { @@ -2074,7 +1715,7 @@ }; }; buildConfigurationList = 058D09A7195D04C000B7D73C /* Build configuration list for PBXProject "AsyncDisplayKit" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 6.3"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( @@ -2086,10 +1727,9 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 058D09AB195D04C000B7D73C /* AsyncDisplayKit */, + B35061D91B010EDF0018CF92 /* AsyncDisplayKit */, 058D09BB195D04C000B7D73C /* AsyncDisplayKitTests */, 057D02BE1AC0A66700C7AC3C /* AsyncDisplayKitTestHost */, - B35061D91B010EDF0018CF92 /* AsyncDisplayKit-iOS */, ); }; /* End PBXProject section */ @@ -2099,7 +1739,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 204C979E1B362CB3002B1083 /* Default-568h@2x.png in Resources */, + 692510141E74FB44003F2DD0 /* Default-568h@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2107,6 +1747,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + CC0F886C1E4286FA00576FED /* ReferenceImages_64 in Resources */, + CC0F886D1E4286FA00576FED /* ReferenceImages_iOS_10 in Resources */, 052EE06B1A15A0D8002C6279 /* TestResources in Resources */, 058D09CA195D04C000B7D73C /* InfoPlist.strings in Resources */, ); @@ -2134,7 +1776,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 3B9D88CDF51B429C8409E4B6 /* [CP] Copy Pods Resources */ = { @@ -2174,142 +1816,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 057D02C71AC0A66700C7AC3C /* AppDelegate.mm in Sources */, + 057D02C71AC0A66700C7AC3C /* AppDelegate.m in Sources */, 057D02C41AC0A66700C7AC3C /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 058D09A8195D04C000B7D73C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 058D0A22195D050800B7D73C /* _ASAsyncTransaction.mm in Sources */, - E52405B31C8FEF03004DC8E7 /* ASLayoutTransition.mm in Sources */, - 8B0768B41CE752EC002E1453 /* ASDefaultPlaybackButton.m in Sources */, - E55D86321CA8A14000A0C26F /* ASLayoutElement.mm in Sources */, - 68FC85E41CE29B7E00EDD713 /* ASTabBarController.m 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.mm 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 */, - 69708BA71D76386D005C3CF9 /* ASEqualityHashHelpers.mm in Sources */, - 058D0A27195D050800B7D73C /* _ASPendingState.mm in Sources */, - 205F0E1A1B37339C007741D0 /* ASAbstractLayoutController.mm in Sources */, - ACF6ED1B1B17843500DA7C62 /* ASBackgroundLayoutSpec.mm in Sources */, - 0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */, - 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */, - AC6456091B0A335000CF11B8 /* ASCellNode.mm in Sources */, - DE8BEAC31C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, - ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */, - 9F98C0251DBDF2A300476D92 /* ASControlTargetAction.m in Sources */, - 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */, - 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */, - DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */, - AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */, - 9CFFC6C21CCAC768006A6476 /* ASTableNode.mm in Sources */, - DE89C1751DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m in Sources */, - 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */, - 68FC85EB1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */, - 058D0A13195D050800B7D73C /* ASControlNode.mm in Sources */, - 464052211A3F83C40061C0BA /* ASDataController.mm in Sources */, - B30BF6531C5964B0004FCD53 /* ASLayoutManager.m in Sources */, - ACF6ED211B17843500DA7C62 /* ASDimension.mm in Sources */, - 8021EC1E1D2B00B100799119 /* UIImage+ASConvenience.m 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 */, - DEC146B81C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */, - 058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */, - AEEC47E21C20C2DD00EC1693 /* ASVideoNode.mm in Sources */, - 0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */, - 464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */, - 257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */, - DE89C1791DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m in Sources */, - 058D0A1A195D050800B7D73C /* ASHighlightOverlayLayer.mm in Sources */, - 058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */, - CC3B208B1C3F7A5400798563 /* ASWeakSet.m in Sources */, - 058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */, - 430E7C911B4C23F100697A4C /* ASIndexPath.m in Sources */, - ACF6ED231B17843500DA7C62 /* ASInsetLayoutSpec.mm in Sources */, - ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.m in Sources */, - 68FC85DF1CE29AB700EDD713 /* ASNavigationController.m in Sources */, - ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */, - CC4981BD1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m in Sources */, - DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */, - 92074A631CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */, - 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */, - ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */, - 257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */, - 0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */, - DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */, - AC6145431D8AFD4F003D62A2 /* ASSection.m in Sources */, - 058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */, - 055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */, - AEB7B01B1C5962EA00662EF4 /* ASDefaultPlayButton.m in Sources */, - CC3B20851C3F76D600798563 /* ASPendingStateController.mm in Sources */, - ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */, - 0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */, - 7A06A73A1C35F08800FE8DAA /* ASRelativeLayoutSpec.mm in Sources */, - 6907C2591DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */, - 69CB62AD1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, - 257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */, - DE89C1711DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m in Sources */, - 055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */, - 044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */, - 257754AE1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm in Sources */, - ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */, - CC4C2A781D88E3BF0039ACAB /* ASTraceEvent.m in Sources */, - 205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */, - 9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, - D785F6631A74327E00291744 /* ASScrollNode.m in Sources */, - E5711A2E1C840C96009619D4 /* ASIndexedNodeContext.mm in Sources */, - 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, - 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */, - ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */, - 257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */, - 257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */, - ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */, - ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */, - 696F01ED1DD2AF450049FBD5 /* ASEventLog.mm in Sources */, - 83A7D95A1D44542100BF333E /* ASWeakMap.m in Sources */, - 257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */, - 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */, - 9C70F2041CDA4EFA007D6C76 /* ASTraitCollection.m in Sources */, - 92074A691CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */, - ACF6ED321B17843500DA7C62 /* ASAbsoluteLayoutSpec.mm in Sources */, - AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */, - 68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */, - 68C215591DE10D330019C4BC /* ASCollectionViewLayoutInspector.m in Sources */, - 9CFFC6C01CCAC73C006A6476 /* ASViewController.mm in Sources */, - 055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */, - 6959433E1D70815300B0EE1F /* ASDisplayNodeLayout.mm in Sources */, - 058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */, - 257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */, - 8BDA5FC61CDBDDE1007D13B2 /* ASVideoPlayerNode.mm in Sources */, - 205F0E221B376416007741D0 /* CoreGraphics+ASConvenience.m in Sources */, - 257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */, - 9CFFC6BE1CCAC52B006A6476 /* ASEnvironment.mm in Sources */, - 697796601D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */, - 058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */, - 205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */, - CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 058D09B8195D04C000B7D73C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2322,6 +1833,7 @@ 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.mm in Sources */, 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */, CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */, + CC034A101E60C9BF00626263 /* ASRectTableTests.m in Sources */, F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */, ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */, 695BE2551DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm in Sources */, @@ -2331,7 +1843,7 @@ 058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */, CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */, AE6987C11DD04E1000B9E458 /* ASPagerNodeTests.m in Sources */, - 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */, + 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.mm in Sources */, 696FCB311D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm in Sources */, 69FEE53D1D95A9AF0086F066 /* ASLayoutElementStyleTests.m in Sources */, CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */, @@ -2357,7 +1869,7 @@ 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */, - 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */, + 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */, AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */, 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */, 058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */, @@ -2377,13 +1889,15 @@ DEB8ED7C1DD003D300DBDE55 /* ASLayoutTransition.mm in Sources */, 9F98C0261DBE29E000476D92 /* ASControlTargetAction.m in Sources */, 9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */, + 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */, 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */, + 690C35671E0567C600069B91 /* ASDimensionDeprecated.mm 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 */, + E516FC801E9FE24200714FF4 /* ASEqualityHashHelpers.mm in Sources */, + 6947B0C51E36B5040007C478 /* ASStackPositionedLayout.mm in Sources */, B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */, AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */, B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */, @@ -2394,26 +1908,29 @@ B35062121B010EFD0018CF92 /* _ASDisplayView.mm in Sources */, DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */, B350624C1B010EFD0018CF92 /* _ASPendingState.mm in Sources */, - 69708BA81D76386D005C3CF9 /* ASEqualityHashHelpers.mm in Sources */, + 698371DC1E4379CD00437585 /* ASNodeController+Beta.m in Sources */, 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */, + E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */, 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, + 690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, - 9C70F2081CDAA3C6007D6C76 /* ASEnvironment.mm in Sources */, B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */, AC47D9421B3B891B00AAEE9D /* ASCellNode.mm in Sources */, + E58E9E451E941D74004CFC59 /* ASCollectionLayoutContext.mm in Sources */, 34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */, 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */, E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */, 68FC85EC1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */, + CC55A7121E52A0F200594372 /* ASResponderChainEnumerator.m in Sources */, 68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */, 9C70F20A1CDBE949007D6C76 /* ASTableNode.mm in Sources */, 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, - 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */, + 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, @@ -2424,26 +1941,27 @@ 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.mm in Sources */, + E5711A301C840C96009619D4 /* ASCollectionElement.mm in Sources */, B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */, + E5E281761E71C845006B67C2 /* ASCollectionLayoutState.m in Sources */, B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */, B35061FF1B010EFD0018CF92 /* ASDisplayNodeExtras.mm in Sources */, B35062011B010EFD0018CF92 /* ASEditableTextNode.mm in Sources */, 254C6B881BF94F8A003EC431 /* ASTextKitRenderer.mm in Sources */, CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */, - B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */, + B350621C1B010EFD0018CF92 /* ASTableLayoutController.m in Sources */, B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */, 9CC606651D24DF9E006581A0 /* NSIndexSet+ASHelpers.m in Sources */, - 92074A641CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */, - DE89C17B1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m in Sources */, + CC0F885F1E4280B800576FED /* _ASCollectionViewCell.m in Sources */, + CC2F65EF1E5FFB1600DA57C9 /* ASMutableElementMap.m in Sources */, B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */, + E58E9E4A1E941DA5004CFC59 /* ASCollectionLayout.mm in Sources */, + 6947B0C01E36B4E30007C478 /* ASStackUnpositionedLayout.mm in Sources */, 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */, B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */, - 254C6B821BF94F8A003EC431 /* ASTextKitComponents.m in Sources */, - 430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */, + 254C6B821BF94F8A003EC431 /* ASTextKitComponents.mm in Sources */, 34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */, AC6145441D8AFD4F003D62A2 /* ASSection.m in Sources */, 34EFC75E1B701BF000AD841F /* ASInternalHelpers.m in Sources */, @@ -2458,8 +1976,7 @@ B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */, B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */, 34EFC76D1B701CF100AD841F /* ASOverlayLayoutSpec.mm in Sources */, - 044285101BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */, - DE89C1731DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m in Sources */, + 044285101BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m in Sources */, B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */, 0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */, 68FC85E61CE29B9400EDD713 /* ASNavigationController.m in Sources */, @@ -2467,36 +1984,39 @@ 34EFC76F1B701CF700AD841F /* ASRatioLayoutSpec.mm in Sources */, 254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */, 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */, + 90FC784F1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm in Sources */, + E5ABAC7C1E8564EE007AC15C /* ASRectTable.m in Sources */, 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */, - B35062091B010EFD0018CF92 /* ASScrollNode.m in Sources */, - 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, + B35062091B010EFD0018CF92 /* ASScrollNode.mm in Sources */, 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */, 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */, - 34EFC7761B701D2A00AD841F /* ASStackPositionedLayout.mm in Sources */, 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */, 696F01EE1DD2AF450049FBD5 /* ASEventLog.mm in Sources */, 9C70F2051CDA4F06007D6C76 /* ASTraitCollection.m in Sources */, 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */, - 34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */, + CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.m in Sources */, + E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m in Sources */, DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, 68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */, - AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */, 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */, - 92074A6A1CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */, + 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */, 68C2155A1DE10D330019C4BC /* ASCollectionViewLayoutInspector.m in Sources */, DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */, B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */, B350620E1B010EFD0018CF92 /* ASTextNode.mm in Sources */, 6959433F1D70815300B0EE1F /* ASDisplayNodeLayout.mm in Sources */, 68355B3E1CB57A60001D4E68 /* ASPINRemoteImageDownloader.m in Sources */, + CC034A141E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m in Sources */, 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.m in Sources */, 254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */, 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */, 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */, + CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.m in Sources */, 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */, - DE89C1771DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m in Sources */, + CC0F885B1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m in Sources */, + 690ED5981E36D118000627C0 /* ASControlNode+tvOS.m in Sources */, 254C6B8A1BF94F8A003EC431 /* ASTextKitRenderer+TextChecking.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2509,16 +2029,6 @@ target = 057D02BE1AC0A66700C7AC3C /* AsyncDisplayKitTestHost */; targetProxy = 057D02E51AC0A67000C7AC3C /* PBXContainerItemProxy */; }; - 058D09C3195D04C000B7D73C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 058D09AB195D04C000B7D73C /* AsyncDisplayKit */; - targetProxy = 058D09C2195D04C000B7D73C /* PBXContainerItemProxy */; - }; - DEACA2B21C425DC400FA9DDF /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 058D09AB195D04C000B7D73C /* AsyncDisplayKit */; - targetProxy = DEACA2B11C425DC400FA9DDF /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -2545,8 +2055,7 @@ "DEBUG=1", "$(inherited)", ); - INFOPLIST_FILE = AsyncDisplayKitTestHost/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + INFOPLIST_FILE = Tests/TestHost/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; @@ -2563,8 +2072,7 @@ 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; + INFOPLIST_FILE = Tests/TestHost/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; @@ -2586,15 +2094,20 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_TEST_COVERAGE_FILES = YES; GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -2607,9 +2120,10 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -2627,70 +2141,32 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_GENERATE_TEST_COVERAGE_FILES = YES; GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; - 058D09D0195D04C000B7D73C /* Debug */ = { - 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_ABOUT_MISSING_NEWLINE = 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 = Debug; - }; - 058D09D1195D04C000B7D73C /* Release */ = { - 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_ABOUT_MISSING_NEWLINE = 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 = Release; - }; 058D09D3195D04C000B7D73C /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */; @@ -2702,18 +2178,14 @@ "$(DEVELOPER_FRAMEWORKS_DIR)", ); GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(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; + INFOPLIST_FILE = "Tests/AsyncDisplayKitTests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost"; @@ -2732,17 +2204,13 @@ "$(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; + INFOPLIST_FILE = "Tests/AsyncDisplayKitTests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost"; @@ -2753,9 +2221,9 @@ B35061EE1B010EDF0018CF92 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_CODE_COVERAGE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -2765,21 +2233,17 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; GCC_NO_COMMON_BLOCKS = YES; - GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = "$(SRCROOT)/AsyncDisplayKit-iOS/Info.plist"; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; + INFOPLIST_FILE = "$(SRCROOT)/Source/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; + MODULEMAP_FILE = Source/AsyncDisplayKit.modulemap; MTL_ENABLE_DEBUG_INFO = YES; + OTHER_CFLAGS = "-Wundef"; 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 = ""; }; @@ -2788,9 +2252,9 @@ B35061EF1B010EDF0018CF92 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; 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"; @@ -2801,17 +2265,16 @@ 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"; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + INFOPLIST_FILE = "$(SRCROOT)/Source/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; + MODULEMAP_FILE = Source/AsyncDisplayKit.modulemap; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-Wundef"; 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 = ""; }; @@ -2831,48 +2294,32 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_GENERATE_TEST_COVERAGE_FILES = YES; GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; 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_ABOUT_MISSING_NEWLINE = 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 = BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */; @@ -2884,17 +2331,13 @@ "$(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; + INFOPLIST_FILE = "Tests/AsyncDisplayKitTests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost"; @@ -2911,8 +2354,7 @@ 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; + INFOPLIST_FILE = Tests/TestHost/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; @@ -2923,9 +2365,9 @@ DB1020841CBCA2AD00FA6FE1 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; 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"; @@ -2936,17 +2378,16 @@ 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"; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + INFOPLIST_FILE = "$(SRCROOT)/Source/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; + MODULEMAP_FILE = Source/AsyncDisplayKit.modulemap; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-Wundef"; 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 = ""; }; @@ -2975,16 +2416,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 058D09CF195D04C000B7D73C /* Build configuration list for PBXNativeTarget "AsyncDisplayKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 058D09D0195D04C000B7D73C /* Debug */, - 058D09D1195D04C000B7D73C /* Release */, - DB1020811CBCA2AD00FA6FE1 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 058D09D2195D04C000B7D73C /* Build configuration list for PBXNativeTarget "AsyncDisplayKitTests" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2995,7 +2426,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B35061ED1B010EDF0018CF92 /* Build configuration list for PBXNativeTarget "AsyncDisplayKit-iOS" */ = { + B35061ED1B010EDF0018CF92 /* Build configuration list for PBXNativeTarget "AsyncDisplayKit" */ = { isa = XCConfigurationList; buildConfigurations = ( B35061EE1B010EDF0018CF92 /* Debug */, diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme deleted file mode 100644 index 193b302afe..0000000000 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme index 3b750e4914..8cf72597a3 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme @@ -14,34 +14,19 @@ buildForAnalyzing = "YES"> - - - - + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -56,17 +41,14 @@ - - @@ -87,8 +69,8 @@ @@ -96,7 +78,7 @@ @@ -112,8 +94,8 @@ diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm deleted file mode 100755 index 8a989c5c8d..0000000000 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ /dev/null @@ -1,596 +0,0 @@ -// -// ASNetworkImageNode.mm -// AsyncDisplayKit -// -// 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 "ASNetworkImageNode.h" - -#import "ASBasicImageDownloader.h" -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASEqualityHelpers.h" -#import "ASInternalHelpers.h" -#import "ASImageContainerProtocolCategories.h" -#import "ASDisplayNodeExtras.h" - -#if PIN_REMOTE_IMAGE -#import "ASPINRemoteImageDownloader.h" -#endif - -static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - -@interface ASNetworkImageNode () -{ - __weak id _cache; - __weak id _downloader; - - // Only access any of these with __instanceLock__. - __weak id _delegate; - - NSURL *_URL; - UIImage *_defaultImage; - - NSUUID *_cacheUUID; - id _downloadIdentifier; - // The download identifier that we have set a progress block on, if any. - id _downloadIdentifierForProgressBlock; - - BOOL _imageLoaded; - CGFloat _currentImageQuality; - CGFloat _renderedImageQuality; - BOOL _shouldRenderProgressImages; - - struct { - unsigned int delegateDidStartFetchingData:1; - unsigned int delegateDidFailWithError:1; - unsigned int delegateDidFinishDecoding:1; - unsigned int delegateDidLoadImage:1; - } _delegateFlags; - - //set on init only - struct { - unsigned int downloaderSupportsNewProtocol:1; - unsigned int downloaderImplementsSetProgress:1; - unsigned int downloaderImplementsSetPriority:1; - unsigned int downloaderImplementsAnimatedImage:1; - } _downloaderFlags; - - struct { - unsigned int cacheSupportsCachedImage:1; - unsigned int cacheSupportsClearing:1; - unsigned int cacheSupportsSynchronousFetch:1; - } _cacheFlags; -} -@end - -@implementation ASNetworkImageNode - -- (instancetype)initWithCache:(id)cache downloader:(id)downloader -{ - if (!(self = [super init])) - return nil; - - _cache = (id)cache; - _downloader = (id)downloader; - - ASDisplayNodeAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion:."); - - _downloaderFlags.downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)]; - - ASDisplayNodeAssert(cache == nil || [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion:"); - - _downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)]; - - _cacheFlags.cacheSupportsCachedImage = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)]; - _cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; - _cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)]; - - _shouldCacheImage = YES; - _shouldRenderProgressImages = YES; - self.shouldBypassEnsureDisplay = YES; - - return self; -} - -- (instancetype)init -{ -#if PIN_REMOTE_IMAGE - return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]]; -#else - return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; -#endif -} - -- (void)dealloc -{ - [self _cancelImageDownload]; -} - -#pragma mark - Public methods -- must lock - -- (void)setURL:(NSURL *)URL -{ - [self setURL:URL resetToDefault:YES]; -} - -- (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset -{ - ASDN::MutexLocker l(__instanceLock__); - - if (ASObjectIsEqual(URL, _URL)) { - return; - } - - [self _cancelImageDownload]; - _imageLoaded = NO; - - _URL = URL; - - BOOL hasURL = _URL == nil; - if (reset || hasURL) { - self.image = _defaultImage; - /* We want to maintain the order that currentImageQuality is set regardless of the calling thread, - so always use a dispatch_async to ensure that we queue the operations in the correct order. - (see comment in displayDidFinish) */ - dispatch_async(dispatch_get_main_queue(), ^{ - self.currentImageQuality = hasURL ? 0.0 : 1.0; - }); - } - - [self setNeedsDataFetch]; -} - -- (NSURL *)URL -{ - ASDN::MutexLocker l(__instanceLock__); - return _URL; -} - -- (void)setDefaultImage:(UIImage *)defaultImage -{ - ASDN::MutexLocker l(__instanceLock__); - - if (ASObjectIsEqual(defaultImage, _defaultImage)) { - return; - } - _defaultImage = defaultImage; - - if (!_imageLoaded) { - BOOL hasURL = _URL == nil; - /* We want to maintain the order that currentImageQuality is set regardless of the calling thread, - so always use a dispatch_async to ensure that we queue the operations in the correct order. - (see comment in displayDidFinish) */ - dispatch_async(dispatch_get_main_queue(), ^{ - self.currentImageQuality = hasURL ? 0.0 : 1.0; - }); - self.image = defaultImage; - } -} - -- (UIImage *)defaultImage -{ - ASDN::MutexLocker l(__instanceLock__); - return _defaultImage; -} - -- (void)setCurrentImageQuality:(CGFloat)currentImageQuality -{ - ASDN::MutexLocker l(__instanceLock__); - _currentImageQuality = currentImageQuality; -} - -- (CGFloat)currentImageQuality -{ - ASDN::MutexLocker l(__instanceLock__); - return _currentImageQuality; -} - -- (void)setRenderedImageQuality:(CGFloat)renderedImageQuality -{ - ASDN::MutexLocker l(__instanceLock__); - _renderedImageQuality = renderedImageQuality; -} - -- (CGFloat)renderedImageQuality -{ - ASDN::MutexLocker l(__instanceLock__); - return _renderedImageQuality; -} - -- (void)setDelegate:(id)delegate -{ - ASDN::MutexLocker l(__instanceLock__); - _delegate = delegate; - - _delegateFlags.delegateDidStartFetchingData = [delegate respondsToSelector:@selector(imageNodeDidStartFetchingData:)]; - _delegateFlags.delegateDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)]; - _delegateFlags.delegateDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]; - _delegateFlags.delegateDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)]; -} - -- (id)delegate -{ - ASDN::MutexLocker l(__instanceLock__); - return _delegate; -} - -- (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages -{ - ASDN::MutexLocker l(__instanceLock__); - if (shouldRenderProgressImages == _shouldRenderProgressImages) { - return; - } - _shouldRenderProgressImages = shouldRenderProgressImages; - - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (BOOL)shouldRenderProgressImages -{ - ASDN::MutexLocker l(__instanceLock__); - return _shouldRenderProgressImages; -} - -- (BOOL)placeholderShouldPersist -{ - ASDN::MutexLocker l(__instanceLock__); - return (self.image == nil && _URL != nil); -} - -/* displayWillStart in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary - in ASMultiplexImageNode as well. */ -- (void)displayWillStartAsynchronously:(BOOL)asynchronously -{ - [super displayWillStartAsynchronously:asynchronously]; - - if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) { - ASDN::MutexLocker l(__instanceLock__); - if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) { - UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image]; - if (result) { - self.image = result; - _imageLoaded = YES; - dispatch_async(dispatch_get_main_queue(), ^{ - _currentImageQuality = 1.0; - }); - } - } - } - - // TODO: Consider removing this; it predates ASInterfaceState, which now ensures that even non-range-managed nodes get a -fetchData call. - [self fetchData]; - - if (self.image == nil && _downloaderFlags.downloaderImplementsSetPriority) { - ASDN::MutexLocker l(__instanceLock__); - if (_downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:_downloadIdentifier]; - } - } -} - -/* visibileStateDidChange in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary - in ASMultiplexImageNode as well. */ -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - ASDN::MutexLocker l(__instanceLock__); - - if (_downloaderFlags.downloaderImplementsSetPriority) { - if (_downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:_downloadIdentifier]; - } - } - - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - ASDN::MutexLocker l(__instanceLock__); - - if (_downloaderFlags.downloaderImplementsSetPriority) { - if (_downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:_downloadIdentifier]; - } - } - - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)clearFetchedData -{ - [super clearFetchedData]; - - { - ASDN::MutexLocker l(__instanceLock__); - - [self _cancelImageDownload]; - [self _clearImage]; - if (_cacheFlags.cacheSupportsClearing) { - [_cache clearFetchedImageFromCacheWithURL:_URL]; - } - } -} - -- (void)fetchData -{ - [super fetchData]; - - { - ASDN::MutexLocker l(__instanceLock__); - [self _lazilyLoadImageIfNecessary]; - } -} - -#pragma mark - Private methods, safe to call without lock - -- (void)handleProgressImage:(UIImage *)progressImage progress:(CGFloat)progress downloadIdentifier:(nullable id)downloadIdentifier -{ - ASDN::MutexLocker l(__instanceLock__); - // Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - self.image = progressImage; - dispatch_async(dispatch_get_main_queue(), ^{ - // See comment in -displayDidFinish for why this must be dispatched to main - self.currentImageQuality = progress; - }); -} - -#pragma mark - Private methods, call with lock held - -- (void)_updateProgressImageBlockOnDownloaderIfNeeded -{ - // If the downloader doesn't do progress, we are done. - if (_downloaderFlags.downloaderImplementsSetProgress == NO) { - return; - } - - // Read state. - BOOL shouldRender = _shouldRenderProgressImages && ASInterfaceStateIncludesVisible(_interfaceState); - id oldDownloadIDForProgressBlock = _downloadIdentifierForProgressBlock; - id newDownloadIDForProgressBlock = shouldRender ? _downloadIdentifier : nil; - - // If we're already bound to the correct download, we're done. - if (ASObjectIsEqual(oldDownloadIDForProgressBlock, newDownloadIDForProgressBlock)) { - return; - } - - // Unbind from the previous download. - if (oldDownloadIDForProgressBlock != nil) { - [_downloader setProgressImageBlock:nil callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:oldDownloadIDForProgressBlock]; - } - - // Bind to the current download. - if (newDownloadIDForProgressBlock != nil) { - __weak __typeof(self) weakSelf = self; - [_downloader setProgressImageBlock:^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) { - [weakSelf handleProgressImage:progressImage progress:progress downloadIdentifier:downloadIdentifier]; - } callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:newDownloadIDForProgressBlock]; - } - - // Update state. - _downloadIdentifierForProgressBlock = newDownloadIDForProgressBlock; -} - -- (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. - UIImage *image = self.image; - CGSize imageSize = image.size; - BOOL shouldReleaseImageOnBackgroundThread = imageSize.width > kMinReleaseImageOnBackgroundSize.width || - imageSize.height > kMinReleaseImageOnBackgroundSize.height; - if (shouldReleaseImageOnBackgroundThread) { - ASPerformBackgroundDeallocation(image); - } - self.animatedImage = nil; - self.image = _defaultImage; - _imageLoaded = NO; - // See comment in -displayDidFinish for why this must be dispatched to main - dispatch_async(dispatch_get_main_queue(), ^{ - self.currentImageQuality = 0.0; - }); -} - -- (void)_cancelImageDownload -{ - if (!_downloadIdentifier) { - return; - } - - if (_downloadIdentifier) { - [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; - } - _downloadIdentifier = nil; - - _cacheUUID = nil; -} - -- (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier))finished -{ - ASPerformBlockOnBackgroundThread(^{ - - ASDN::MutexLocker l(__instanceLock__); - if (_downloaderFlags.downloaderSupportsNewProtocol) { - _downloadIdentifier = [_downloader downloadImageWithURL:_URL - callbackQueue:dispatch_get_main_queue() - downloadProgress:NULL - completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { - if (finished != NULL) { - finished(imageContainer, error, downloadIdentifier); - } - }]; - } - - [self _updateProgressImageBlockOnDownloaderIfNeeded]; - - }); -} - -- (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(__instanceLock__); - if (_delegateFlags.delegateDidStartFetchingData) { - [_delegate imageNodeDidStartFetchingData:self]; - } - } - - if (_URL.isFileURL) { - { - ASDN::MutexLocker l(__instanceLock__); - - dispatch_async(dispatch_get_main_queue(), ^{ - if (self.shouldCacheImage) { - self.image = [UIImage imageNamed:_URL.path.lastPathComponent]; - } else { - // First try to load the path directly, for efficiency assuming a developer who - // doesn't want caching is trying to be as minimal as possible. - UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:_URL.path]; - if (nonAnimatedImage == nil) { - // If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the - // extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage. - NSString *filename = [[NSBundle mainBundle] pathForResource:_URL.path.lastPathComponent ofType:nil]; - if (filename != nil) { - nonAnimatedImage = [UIImage imageWithContentsOfFile:filename]; - } - } - - // If the file may be an animated gif and then created an animated image. - id animatedImage = nil; - if (_downloaderFlags.downloaderImplementsAnimatedImage) { - NSData *data = [NSData dataWithContentsOfURL:_URL]; - if (data != nil) { - animatedImage = [_downloader animatedImageWithData:data]; - - if ([animatedImage respondsToSelector:@selector(isDataSupported:)] && [animatedImage isDataSupported:data] == NO) { - animatedImage = nil; - } - } - } - - if (animatedImage != nil) { - self.animatedImage = animatedImage; - } else { - self.image = nonAnimatedImage; - } - } - - _imageLoaded = YES; - /* We want to maintain the order that currentImageQuality is set regardless of the calling thread, - so always use a dispatch_async to ensure that we queue the operations in the correct order. - (see comment in displayDidFinish) */ - dispatch_async(dispatch_get_main_queue(), ^{ - self.currentImageQuality = 1.0; - }); - if (_delegateFlags.delegateDidLoadImage) { - [_delegate imageNode:self didLoadImage:self.image]; - } - }); - } - } else { - __weak __typeof__(self) weakSelf = self; - void (^finished)(id , NSError *, id downloadIdentifier) = ^(id imageContainer, NSError *error, id downloadIdentifier) { - __typeof__(self) strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - - ASDN::MutexLocker l(strongSelf->__instanceLock__); - - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - - if (imageContainer != nil) { - strongSelf->_imageLoaded = YES; - if ([imageContainer asdk_animatedImageData] && _downloaderFlags.downloaderImplementsAnimatedImage) { - strongSelf.animatedImage = [_downloader animatedImageWithData:[imageContainer asdk_animatedImageData]]; - } else { - strongSelf.image = [imageContainer asdk_image]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - strongSelf->_currentImageQuality = 1.0; - }); - } - - strongSelf->_downloadIdentifier = nil; - - strongSelf->_cacheUUID = nil; - - if (imageContainer != nil) { - if (strongSelf->_delegateFlags.delegateDidLoadImage) { - [strongSelf->_delegate imageNode:strongSelf didLoadImage:strongSelf.image]; - } - } - else if (error && strongSelf->_delegateFlags.delegateDidFailWithError) { - [strongSelf->_delegate imageNode:strongSelf didFailWithError:error]; - } - }; - - if (_cache != nil) { - NSUUID *cacheUUID = [NSUUID UUID]; - _cacheUUID = cacheUUID; - - void (^cacheCompletion)(id ) = ^(id imageContainer) { - // If the cache UUID changed, that means this request was cancelled. - if (!ASObjectIsEqual(_cacheUUID, cacheUUID)) { - return; - } - - if ([imageContainer asdk_image] == nil && _downloader != nil) { - [self _downloadImageWithCompletion:finished]; - } else { - finished(imageContainer, nil, nil); - } - }; - - if (_cacheFlags.cacheSupportsCachedImage) { - [_cache cachedImageWithURL:_URL - callbackQueue:dispatch_get_main_queue() - completion:cacheCompletion]; - } - } else { - [self _downloadImageWithCompletion:finished]; - } - } - } -} - -#pragma mark - ASDisplayNode+Subclasses - -- (void)displayDidFinish -{ - [super displayDidFinish]; - - ASDN::MutexLocker l(__instanceLock__); - if (_delegateFlags.delegateDidFinishDecoding && self.layer.contents != nil) { - /* We store the image quality in _currentImageQuality whenever _image is set. On the following displayDidFinish, we'll know that - _currentImageQuality is the quality of the image that has just finished rendering. In order for this to be accurate, we - need to be sure we are on main thread when we set _currentImageQuality. Otherwise, it is possible for _currentImageQuality - to be modified at a point where it is too late to cancel the main thread's previous display (the final sentinel check has passed), - but before the displayDidFinish of the previous display pass is called. In this situation, displayDidFinish would be called and we - would set _renderedImageQuality to the new _currentImageQuality, but the actual quality of the rendered image should be the previous - value stored in _currentImageQuality. */ - - _renderedImageQuality = _currentImageQuality; - [self.delegate imageNodeDidFinishDecoding:self]; - } -} - -@end diff --git a/AsyncDisplayKit/ASPagerFlowLayout.m b/AsyncDisplayKit/ASPagerFlowLayout.m deleted file mode 100644 index c958b084d5..0000000000 --- a/AsyncDisplayKit/ASPagerFlowLayout.m +++ /dev/null @@ -1,93 +0,0 @@ -// -// ASPagerFlowLayout.m -// AsyncDisplayKit -// -// Created by Levi McCallum on 2/12/16. -// -// 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 "ASPagerFlowLayout.h" - -@interface ASPagerFlowLayout () { - BOOL _didRotate; - CGRect _cachedCollectionViewBounds; - NSIndexPath *_currentIndexPath; -} - -@end - -@implementation ASPagerFlowLayout - -- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity -{ - NSInteger currentPage = ceil(proposedContentOffset.x / self.collectionView.bounds.size.width); - _currentIndexPath = [NSIndexPath indexPathForItem:currentPage inSection:0]; - - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity]; -} - - -- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds -{ - // Cache the current page if a rotation did happen. This happens before the rotation animation - // is occuring and the bounds changed so we use this as an opportunity to cache the current index path - if (_cachedCollectionViewBounds.size.width != self.collectionView.bounds.size.width) { - _cachedCollectionViewBounds = self.collectionView.bounds; - - // Figurring out current page based on the old bounds visible space - CGRect visibleRect = oldBounds; - - CGFloat visibleXCenter = CGRectGetMidX(visibleRect); - NSArray *layoutAttributes = [self layoutAttributesForElementsInRect:visibleRect]; - for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) { - if ([attributes representedElementCategory] == UICollectionElementCategoryCell && attributes.center.x == visibleXCenter) { - _currentIndexPath = attributes.indexPath; - break; - } - } - - _didRotate = YES; - } - - [super prepareForAnimatedBoundsChange:oldBounds]; -} -- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset -{ - // Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should - // try to use the current index path to not end up setting the target content offset to something in between pages - if (_didRotate || (!self.collectionView.isDecelerating && !self.collectionView.isTracking)) { - _didRotate = NO; - if (_currentIndexPath) { - return [self _targetContentOffsetForItemAtIndexPath:_currentIndexPath proposedContentOffset:proposedContentOffset]; - } - } - - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; -} - -- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset -{ - if ([self _dataSourceIsEmpty]) { - return proposedContentOffset; - } - - UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:_currentIndexPath]; - if (attributes == nil) { - return proposedContentOffset; - } - - CGFloat xOffset = (CGRectGetWidth(self.collectionView.bounds) - CGRectGetWidth(attributes.frame)) / 2.0; - return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y); -} - -- (BOOL)_dataSourceIsEmpty -{ - return ([self.collectionView numberOfSections] == 0 || - [self.collectionView numberOfItemsInSection:0] == 0); -} - -@end diff --git a/AsyncDisplayKit/ASScrollNode.h b/AsyncDisplayKit/ASScrollNode.h deleted file mode 100644 index 00faced970..0000000000 --- a/AsyncDisplayKit/ASScrollNode.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// ASScrollNode.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Simple node that wraps UIScrollView. - */ -@interface ASScrollNode : ASDisplayNode - -/** - * @abstract The node's UIScrollView. - */ -@property (nonatomic, readonly, strong) UIScrollView *view; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASScrollNode.m b/AsyncDisplayKit/ASScrollNode.m deleted file mode 100644 index aed6d8379e..0000000000 --- a/AsyncDisplayKit/ASScrollNode.m +++ /dev/null @@ -1,36 +0,0 @@ -// -// ASScrollNode.m -// AsyncDisplayKit -// -// 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 "ASScrollNode.h" -#import "_ASDisplayLayer.h" - -@interface ASScrollView : UIScrollView -@end - -@implementation ASScrollView - -+ (Class)layerClass -{ - return [_ASDisplayLayer class]; -} - -@end - -@implementation ASScrollNode -@dynamic view; - -- (instancetype)init -{ - return [super initWithViewBlock:^UIView *{ - return [[ASScrollView alloc] init]; - } didLoadBlock:nil]; -} - -@end diff --git a/AsyncDisplayKit/AsyncDisplayKit+Debug.h b/AsyncDisplayKit/AsyncDisplayKit+Debug.h deleted file mode 100644 index 3610d4ecd6..0000000000 --- a/AsyncDisplayKit/AsyncDisplayKit+Debug.h +++ /dev/null @@ -1,79 +0,0 @@ -// -// AsyncDisplayKit+Debug.h -// AsyncDisplayKit -// -// Created by Hannah Troisi on 3/7/16. -// -// 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 "ASDisplayNode.h" -#import "ASControlNode.h" -#import "ASImageNode.h" -#import "ASRangeController.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface ASDisplayNode (Visualization) -@property (nonatomic, assign) BOOL shouldVisualizeLayoutSpecs; -@property (nonatomic, assign) BOOL shouldCacheLayoutSpec; - -@end - -@interface ASImageNode (Debugging) - -/** - * Enables an ASImageNode debug label that shows the ratio of pixels in the source image to those in - * the displayed bounds (including cropRect). This helps detect excessive image fetching / downscaling, - * as well as upscaling (such as providing a URL not suitable for a Retina device). For dev purposes only. - * @param enabled Specify YES to show the label on all ASImageNodes with non-1.0x source-to-bounds pixel ratio. - */ -+ (void)setShouldShowImageScalingOverlay:(BOOL)show; -+ (BOOL)shouldShowImageScalingOverlay; - -@end - -@interface ASControlNode (Debugging) - -/** - * Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only. - * NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!! - * Overlay = translucent GREEN color, - * edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE, - * edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond - * overlay rect, but can't be visualized). - * @param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class. - */ -+ (void)setEnableHitTestDebug:(BOOL)enable; -+ (BOOL)enableHitTestDebug; - -@end - -@interface ASRangeController (Debugging) - -/** - * Class method to enable a visualization overlay of the all ASRangeController's tuning parameters. For dev purposes only. - * To use, message ASRangeController in the AppDelegate --> [ASRangeController setShouldShowRangeDebugOverlay:YES]; - * @param enable Specify YES to make this debug feature enabled when messaging the ASRangeController class. - */ -+ (void)setShouldShowRangeDebugOverlay:(BOOL)show; -+ (BOOL)shouldShowRangeDebugOverlay; - -+ (void)layoutDebugOverlayIfNeeded; - -- (void)addRangeControllerToRangeDebugOverlay; - -- (void)updateRangeController:(ASRangeController *)controller - withScrollableDirections:(ASScrollDirection)scrollableDirections - scrollDirection:(ASScrollDirection)direction - rangeMode:(ASLayoutRangeMode)mode - displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters - preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters - interfaceState:(ASInterfaceState)interfaceState; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch b/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch deleted file mode 100644 index 0062314263..0000000000 --- a/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch +++ /dev/null @@ -1,19 +0,0 @@ -// -// Prefix header -// -// The contents of this file are implicitly included at the beginning of every source file. -// - -#ifdef __OBJC__ - #import -#endif - - -// CocoaPods has a preproceessor macro for PIN_REMOTE_IMAGE, if already defined, okay -#ifndef PIN_REMOTE_IMAGE - -// For Carthage or manual builds, this will define PIN_REMOTE_IMAGE if the header is available in the -// search path e.g. they've dragged in the framework (technically this will not be able to detect if -// a user does not include the framework in the link binary with build step). -#define PIN_REMOTE_IMAGE __has_include() -#endif diff --git a/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h b/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h deleted file mode 100644 index 69bae8c6b9..0000000000 --- a/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// ASLayoutElementInspectorCell.h -// AsyncDisplayKit -// -// Created by Hannah Troisi on 3/27/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import - -typedef NS_ENUM(NSInteger, ASLayoutElementPropertyType) { - ASLayoutElementPropertyFlexGrow = 0, - ASLayoutElementPropertyFlexShrink, - ASLayoutElementPropertyAlignSelf, - ASLayoutElementPropertyFlexBasis, - ASLayoutElementPropertySpacingBefore, - ASLayoutElementPropertySpacingAfter, - ASLayoutElementPropertyAscender, - ASLayoutElementPropertyDescender, - ASLayoutElementPropertyCount -}; - -@interface ASLayoutElementInspectorCell : ASCellNode - -- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id)layoutable NS_DESIGNATED_INITIALIZER; - -@end - diff --git a/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m b/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m deleted file mode 100644 index ab5f191f2d..0000000000 --- a/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m +++ /dev/null @@ -1,569 +0,0 @@ -// -// ASLayoutElementInspectorCell.m -// AsyncDisplayKit -// -// Created by Hannah Troisi on 3/27/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import "ASLayoutElementInspectorCell.h" -#import - -typedef NS_ENUM(NSInteger, CellDataType) { - CellDataTypeBool, - CellDataTypeFloat, -}; - -__weak static ASLayoutElementInspectorCell *__currentlyOpenedCell = nil; - -@protocol InspectorCellEditingBubbleProtocol -- (void)valueChangedToIndex:(NSUInteger)index; -@end - -@interface ASLayoutElementInspectorCellEditingBubble : ASDisplayNode -@property (nonatomic, strong, readwrite) id delegate; -- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray *)options currentOptionIndex:(NSUInteger)currentOption; -- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current -;@end - -@interface ASLayoutElementInspectorCell () -@end - -@implementation ASLayoutElementInspectorCell -{ - ASLayoutElementPropertyType _propertyType; - CellDataType _dataType; - id _layoutElementToEdit; - - ASButtonNode *_buttonNode; - ASTextNode *_textNode; - ASTextNode *_textNode2; - - ASLayoutElementInspectorCellEditingBubble *_textBubble; -} - -#pragma mark - Lifecycle - -- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id)layoutElement -{ - self = [super init]; - if (self) { - - _propertyType = property; - _dataType = [ASLayoutElementInspectorCell dataTypeForProperty:property]; - _layoutElementToEdit = layoutElement; - - self.automaticallyManagesSubnodes = YES; - - _buttonNode = [self makeBtnNodeWithTitle:[ASLayoutElementInspectorCell propertyStringForPropertyType:property]]; - [_buttonNode addTarget:self action:@selector(buttonTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; - - _textNode = [[ASTextNode alloc] init]; - _textNode.attributedText = [ASLayoutElementInspectorCell propertyValueAttributedStringForProperty:property withLayoutElement:layoutElement]; - - [self updateButtonStateForProperty:property withLayoutElement:layoutElement]; - - _textNode2 = [[ASTextNode alloc] init]; - _textNode2.attributedText = [ASLayoutElementInspectorCell propertyValueDetailAttributedStringForProperty:property withLayoutElement:layoutElement]; - - } - return self; -} - -- (void)updateButtonStateForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement -{ - if (property == ASLayoutElementPropertyFlexGrow) { - _buttonNode.selected = layoutElement.style.flexGrow; - } - else if (property == ASLayoutElementPropertyFlexShrink) { - _buttonNode.selected = layoutElement.style.flexShrink; - } -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - ASStackLayoutSpec *horizontalSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; - horizontalSpec.children = @[_buttonNode, _textNode]; - horizontalSpec.style.flexGrow = 1.0; - horizontalSpec.alignItems = ASStackLayoutAlignItemsCenter; - horizontalSpec.justifyContent = ASStackLayoutJustifyContentSpaceBetween; - - ASLayoutSpec *childSpec; - if (_textBubble) { - ASStackLayoutSpec *verticalSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; - verticalSpec.children = @[horizontalSpec, _textBubble]; - verticalSpec.spacing = 8; - verticalSpec.style.flexGrow = 1.0; - _textBubble.style.flexGrow = 1.0; - childSpec = verticalSpec; - } else { - childSpec = horizontalSpec; - } - ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(2, 4, 2, 4) child:childSpec]; - insetSpec.style.flexGrow =1.0; - - return insetSpec; -} - -+ (NSAttributedString *)propertyValueAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement -{ - NSString *valueString; - - switch (property) { - case ASLayoutElementPropertyFlexGrow: - valueString = layoutElement.style.flexGrow ? @"YES" : @"NO"; - break; - case ASLayoutElementPropertyFlexShrink: - valueString = layoutElement.style.flexShrink ? @"YES" : @"NO"; - break; - case ASLayoutElementPropertyAlignSelf: - valueString = [ASLayoutElementInspectorCell alignSelfEnumValueString:layoutElement.style.alignSelf]; - break; - case ASLayoutElementPropertyFlexBasis: - if (layoutElement.style.flexBasis.unit && layoutElement.style.flexBasis.value) { // ENUM TYPE - valueString = [NSString stringWithFormat:@"%0.0f %@", layoutElement.style.flexBasis.value, - [ASLayoutElementInspectorCell ASRelativeDimensionEnumString:layoutElement.style.alignSelf]]; - } else { - valueString = @"0 pts"; - } - break; - case ASLayoutElementPropertySpacingBefore: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingBefore]; - break; - case ASLayoutElementPropertySpacingAfter: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingAfter]; - break; - case ASLayoutElementPropertyAscender: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.ascender]; - break; - case ASLayoutElementPropertyDescender: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.descender]; - break; - default: - valueString = @"?"; - break; - } - return [ASLayoutElementInspectorCell attributedStringFromString:valueString]; -} - -+ (NSAttributedString *)propertyValueDetailAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement -{ - NSString *valueString; - - switch (property) { - case ASLayoutElementPropertyFlexGrow: - case ASLayoutElementPropertyFlexShrink: - case ASLayoutElementPropertyAlignSelf: - case ASLayoutElementPropertyFlexBasis: - case ASLayoutElementPropertySpacingBefore: - case ASLayoutElementPropertySpacingAfter: - case ASLayoutElementPropertyAscender: - case ASLayoutElementPropertyDescender: - default: - return nil; - } - return [ASLayoutElementInspectorCell attributedStringFromString:valueString]; -} - -- (void)endEditingValue -{ - _textBubble = nil; - __currentlyOpenedCell = nil; - _buttonNode.selected = NO; - [self setNeedsLayout]; -} - -- (void)beginEditingValue -{ - _textBubble.delegate = self; - __currentlyOpenedCell = self; - [self setNeedsLayout]; -} - -- (void)valueChangedToIndex:(NSUInteger)index -{ - switch (_propertyType) { - - case ASLayoutElementPropertyAlignSelf: - _layoutElementToEdit.style.alignSelf = index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[ASLayoutElementInspectorCell alignSelfEnumValueString:index]]; - break; - - case ASLayoutElementPropertySpacingBefore: - _layoutElementToEdit.style.spacingBefore = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]]; - break; - - case ASLayoutElementPropertySpacingAfter: - _layoutElementToEdit.style.spacingAfter = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]]; - break; - - case ASLayoutElementPropertyAscender: - _layoutElementToEdit.style.ascender = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]]; - break; - - case ASLayoutElementPropertyDescender: - _layoutElementToEdit.style.descender = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]]; - break; - - default: - break; - } - - [self setNeedsLayout]; -} - -#pragma mark - gesture handling - -- (void)buttonTapped:(ASButtonNode *)sender -{ - BOOL selfIsEditing = (self == __currentlyOpenedCell); - [__currentlyOpenedCell endEditingValue]; - if (selfIsEditing) { - sender.selected = NO; - return; - } - -// NSUInteger currentAlignSelfValue; -// NSUInteger nextAlignSelfValue; -// CGFloat newValue; - - sender.selected = !sender.selected; - switch (_propertyType) { - - case ASLayoutElementPropertyFlexGrow: - _layoutElementToEdit.style.flexGrow = sender.isSelected ? 1.0 : 0.0; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"]; - break; - - case ASLayoutElementPropertyFlexShrink: - _layoutElementToEdit.style.flexShrink = sender.isSelected ? 1.0 : 0.0; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"]; - break; - - case ASLayoutElementPropertyAlignSelf: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithEnumOptions:YES - enumStrings:[ASLayoutElementInspectorCell alignSelfEnumStringArray] - currentOptionIndex:_layoutElementToEdit.style.alignSelf]; - - [self beginEditingValue]; -// if ([self layoutSpec]) { -// currentAlignSelfValue = [[self layoutSpec] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self layoutSpec] setAlignSelf:nextAlignSelfValue]; -// -// } else if ([self node]) { -// currentAlignSelfValue = [[self node] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self node] setAlignSelf:nextAlignSelfValue]; -// } - break; - - case ASLayoutElementPropertySpacingBefore: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingBefore]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]]; - break; - - case ASLayoutElementPropertySpacingAfter: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingAfter]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]]; - break; - - - case ASLayoutElementPropertyAscender: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.ascender]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]]; - break; - - case ASLayoutElementPropertyDescender: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.descender]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]]; - break; - - default: - break; - } - [self setNeedsLayout]; -} - -#pragma mark - cast layoutElementToEdit - -- (ASDisplayNode *)node -{ - if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeDisplayNode) { - return (ASDisplayNode *)_layoutElementToEdit; - } - return nil; -} - -- (ASLayoutSpec *)layoutSpec -{ - if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeLayoutSpec) { - return (ASLayoutSpec *)_layoutElementToEdit; - } - return nil; -} - -#pragma mark - data / property type helper methods - -+ (CellDataType)dataTypeForProperty:(ASLayoutElementPropertyType)property -{ - switch (property) { - - case ASLayoutElementPropertyFlexGrow: - case ASLayoutElementPropertyFlexShrink: - return CellDataTypeBool; - - case ASLayoutElementPropertySpacingBefore: - case ASLayoutElementPropertySpacingAfter: - case ASLayoutElementPropertyAscender: - case ASLayoutElementPropertyDescender: - return CellDataTypeFloat; - - default: - break; - } - return CellDataTypeBool; -} - -+ (NSString *)propertyStringForPropertyType:(ASLayoutElementPropertyType)property -{ - NSString *string; - switch (property) { - case ASLayoutElementPropertyFlexGrow: - string = @"FlexGrow"; - break; - case ASLayoutElementPropertyFlexShrink: - string = @"FlexShrink"; - break; - case ASLayoutElementPropertyAlignSelf: - string = @"AlignSelf"; - break; - case ASLayoutElementPropertyFlexBasis: - string = @"FlexBasis"; - break; - case ASLayoutElementPropertySpacingBefore: - string = @"SpacingBefore"; - break; - case ASLayoutElementPropertySpacingAfter: - string = @"SpacingAfter"; - break; - case ASLayoutElementPropertyAscender: - string = @"Ascender"; - break; - case ASLayoutElementPropertyDescender: - string = @"Descender"; - break; - default: - string = @"Unknown"; - break; - } - return string; -} - -+ (NSDictionary *)alignSelfTypeNames -{ - return @{@(ASStackLayoutAlignSelfAuto) : @"Auto", - @(ASStackLayoutAlignSelfStart) : @"Start", - @(ASStackLayoutAlignSelfEnd) : @"End", - @(ASStackLayoutAlignSelfCenter) : @"Center", - @(ASStackLayoutAlignSelfStretch) : @"Stretch"}; -} - -+ (NSString *)alignSelfEnumValueString:(NSUInteger)type -{ - return [[self class] alignSelfTypeNames][@(type)]; -} - -+ (NSArray *)alignSelfEnumStringArray -{ - return @[@"ASStackLayoutAlignSelfAuto", - @"ASStackLayoutAlignSelfStart", - @"ASStackLayoutAlignSelfEnd", - @"ASStackLayoutAlignSelfCenter", - @"ASStackLayoutAlignSelfStretch"]; -} - -+ (NSDictionary *)ASRelativeDimensionTypeNames -{ - return @{@(ASDimensionUnitPoints) : @"pts", - @(ASDimensionUnitFraction) : @"%"}; -} - -+ (NSString *)ASRelativeDimensionEnumString:(NSUInteger)type -{ - return [[self class] ASRelativeDimensionTypeNames][@(type)]; -} - -#pragma mark - formatting helper methods - -+ (NSAttributedString *)attributedStringFromString:(NSString *)string -{ - return [ASLayoutElementInspectorCell attributedStringFromString:string withTextColor:[UIColor whiteColor]]; -} - -+ (NSAttributedString *)attributedStringFromString:(NSString *)string withTextColor:(nullable UIColor *)color -{ - NSDictionary *attributes = @{NSForegroundColorAttributeName : color, - NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]}; - - return [[NSAttributedString alloc] initWithString:string attributes:attributes]; -} - -- (ASButtonNode *)makeBtnNodeWithTitle:(NSString *)title -{ - UIColor *orangeColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; - UIImage *orangeStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:orangeColor - borderColor:[UIColor whiteColor] - borderWidth:3]; - UIImage *greyStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor darkGrayColor] - borderColor:[UIColor lightGrayColor] - borderWidth:3]; - UIImage *clearStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor clearColor] - borderColor:[UIColor whiteColor] - borderWidth:3]; - ASButtonNode *btn = [[ASButtonNode alloc] init]; - btn.contentEdgeInsets = UIEdgeInsetsMake(5, 5, 5, 5); - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title] forState:ASControlStateNormal]; - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title withTextColor:[UIColor lightGrayColor]] forState:ASControlStateDisabled]; - [btn setBackgroundImage:clearStretchBtnImg forState:ASControlStateNormal]; - [btn setBackgroundImage:orangeStretchBtnImg forState:ASControlStateSelected]; - [btn setBackgroundImage:greyStretchBtnImg forState:ASControlStateDisabled]; - - return btn; -} - -#define CORNER_RADIUS 3 -+ (UIImage *)imageForButtonWithBackgroundColor:(UIColor *)backgroundColor borderColor:(UIColor *)borderColor borderWidth:(CGFloat)width -{ - CGSize unstretchedSize = CGSizeMake(2 * CORNER_RADIUS + 1, 2 * CORNER_RADIUS + 1); - CGRect rect = (CGRect) {CGPointZero, unstretchedSize}; - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:CORNER_RADIUS]; - - // create a graphics context for the following status button - UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0); - - [path addClip]; - [backgroundColor setFill]; - [path fill]; - - path.lineWidth = width; - [borderColor setStroke]; - [path stroke]; - - UIImage *btnImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return [btnImage stretchableImageWithLeftCapWidth:CORNER_RADIUS topCapHeight:CORNER_RADIUS]; -} - -@end - - - -@implementation ASLayoutElementInspectorCellEditingBubble -{ - NSMutableArray *_textNodes; - ASDisplayNode *_slider; -} - -- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray *)options currentOptionIndex:(NSUInteger)currentOption -{ - self = [super init]; - if (self) { - self.automaticallyManagesSubnodes = YES; - self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; - - _textNodes = [[NSMutableArray alloc] init]; - int index = 0; - for (NSString *optionStr in options) { - ASButtonNode *btn = [[ASButtonNode alloc] init]; - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr] forState:ASControlStateNormal]; - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr withTextColor:[UIColor redColor]] - forState:ASControlStateSelected]; - [btn addTarget:self action:@selector(enumOptionSelected:) forControlEvents:ASControlNodeEventTouchUpInside]; - btn.selected = (index == currentOption) ? YES : NO; - [_textNodes addObject:btn]; - index++; - } - } - return self; -} - -- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current -{ - if (self = [super init]) { - self.userInteractionEnabled = YES; - self.automaticallyManagesSubnodes = YES; - self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; - - __weak id weakSelf = self; - _slider = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{ - UISlider *slider = [[UISlider alloc] init]; - slider.minimumValue = min; - slider.maximumValue = max; - slider.value = current; - [slider addTarget:weakSelf action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged]; - - return slider; - }]; - _slider.userInteractionEnabled = YES; - } - return self; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - _slider.style.preferredSize = CGSizeMake(constrainedSize.max.width, 25); - - NSMutableArray *children = [[NSMutableArray alloc] init]; - if (_textNodes) { - ASStackLayoutSpec *textStack = [ASStackLayoutSpec verticalStackLayoutSpec]; - textStack.children = _textNodes; - textStack.spacing = 2; - [children addObject:textStack]; - } - if (_slider) { - _slider.style.flexGrow = 1.0; - [children addObject:_slider]; - } - - ASStackLayoutSpec *verticalStackSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; - verticalStackSpec.children = children; - verticalStackSpec.spacing = 2; - verticalStackSpec.style.flexGrow = 1.0; - verticalStackSpec.style.alignSelf = ASStackLayoutAlignSelfStretch; - - ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(8, 8, 8, 8) child:verticalStackSpec]; - - return insetSpec; -} - -#pragma mark - gesture handling -- (void)enumOptionSelected:(ASButtonNode *)sender -{ - sender.selected = !sender.selected; - for (ASButtonNode *node in _textNodes) { - if (node != sender) { - node.selected = NO; - } - } - [self.delegate valueChangedToIndex:[_textNodes indexOfObject:sender]]; - [self setNeedsLayout]; -} - -- (void)sliderValueChanged:(UISlider *)sender -{ - [self.delegate valueChangedToIndex:roundf(sender.value)]; -} - -@end - - diff --git a/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.h b/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.h deleted file mode 100644 index 5a27945329..0000000000 --- a/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// ASLayoutElementInspectorNode.h -// Sample -// -// Created by Hannah Troisi on 3/19/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import - -@protocol ASLayoutElementInspectorNodeDelegate - -- (void)toggleVisualization:(BOOL)toggle; - -@end - -@interface ASLayoutElementInspectorNode : ASDisplayNode - -@property (nonatomic, strong) id layoutElementToEdit; -@property (nonatomic, strong) id delegate; -@property (nonatomic, assign) CGFloat vizNodeInsetSize; - -+ (instancetype)sharedInstance; - -@end diff --git a/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m b/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m deleted file mode 100644 index 58be55813a..0000000000 --- a/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m +++ /dev/null @@ -1,406 +0,0 @@ -// -// ASLayoutElementInspectorNode.m -// Sample -// -// Created by Hannah Troisi on 3/19/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import "ASLayoutElementInspectorNode.h" -#import "ASLayoutElementInspectorCell.h" -#import "ASDisplayNode+Beta.h" -#import "ASLayoutSpec+Debug.h" -#import - -@interface ASLayoutElementInspectorNode () -@end - -@implementation ASLayoutElementInspectorNode -{ - ASTableNode *_tableNode; -} - -#pragma mark - class methods -+ (instancetype)sharedInstance -{ - static ASLayoutElementInspectorNode *__inspector = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - __inspector = [[ASLayoutElementInspectorNode alloc] init]; - }); - - return __inspector; -} - -#pragma mark - lifecycle -- (instancetype)init -{ - self = [super init]; - if (self) { - - _tableNode = [[ASTableNode alloc] init]; - _tableNode.delegate = self; - _tableNode.dataSource = self; - - [self addSubnode:_tableNode]; // required because of manual layout - } - return self; -} - -- (void)didLoad -{ - [super didLoad]; - _tableNode.view.backgroundColor = [UIColor colorWithRed:40/255.0 green:43/255.0 blue:53/255.0 alpha:1]; - _tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; - _tableNode.view.allowsSelection = NO; - _tableNode.view.sectionHeaderHeight = 40; -} - -- (void)layout -{ - [super layout]; - _tableNode.frame = self.bounds; -} - -#pragma mark - intstance methods -- (void)setLayoutElementToEdit:(id)layoutElementToEdit -{ - if (_layoutElementToEdit != layoutElementToEdit) { - _layoutElementToEdit = layoutElementToEdit; - } - [_tableNode reloadData]; -} - -#pragma mark - ASTableDataSource - -- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (indexPath.section == 0) { - NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1], - NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]}; - ASTextCellNode *textCell = [[ASTextCellNode alloc] initWithAttributes:attributes insets:UIEdgeInsetsMake(0, 4, 0, 0)]; - textCell.text = [_layoutElementToEdit description]; - return textCell; - } else { - return [[ASLayoutElementInspectorCell alloc] initWithProperty:(ASLayoutElementPropertyType)indexPath.row layoutElementToEdit:_layoutElementToEdit]; - } -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - if (section == 0) { - return 1; - } else { - return ASLayoutElementPropertyCount; - } -} - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return 2; -} - -- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section -{ - UILabel *headerTitle = [[UILabel alloc] initWithFrame:CGRectZero]; - - NSString *title; - if (section == 0) { - title = @" Item"; - } else { - title = @" Properties"; - } - - NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor], - NSFontAttributeName : [UIFont fontWithName:@"Menlo-Bold" size:12]}; - headerTitle.attributedText = [[NSAttributedString alloc] initWithString:title attributes:attributes]; - - return headerTitle; -} - -//- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -//{ -// // navigate layout hierarchy -// -// _parentNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter; -// _childNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter; -// -// ASStackLayoutSpec *horizontalStackNav = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStackNav.style.flexGrow = 1.0; -// horizontalStackNav.alignSelf = ASStackLayoutAlignSelfCenter; -// horizontalStackNav.children = @[_siblingNodeLefttNavBtn, _siblingNodeRightNavBtn]; -// -// ASStackLayoutSpec *horizontalStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStack.style.flexGrow = 1.0; -// ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; -// -// spacer.style.flexGrow = 1.0; -// horizontalStack.children = @[_flexGrowBtn, spacer]; -// _flexGrowValue.alignSelf = ASStackLayoutAlignSelfEnd; // FIXME: make framework give a warning if you use ASAlignmentBottom!!!!! -// -// ASStackLayoutSpec *horizontalStack2 = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStack2.style.flexGrow = 1.0; -// horizontalStack2.children = @[_flexShrinkBtn, spacer]; -// _flexShrinkValue.alignSelf = ASStackLayoutAlignSelfEnd; -// -// ASStackLayoutSpec *horizontalStack3 = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStack3.style.flexGrow = 1.0; -// horizontalStack3.children = @[_flexBasisBtn, spacer, _flexBasisValue]; -// _flexBasisValue.alignSelf = ASStackLayoutAlignSelfEnd; -// -// ASStackLayoutSpec *itemDescriptionStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// itemDescriptionStack.children = @[_itemDescription]; -// itemDescriptionStack.spacing = 5; -// itemDescriptionStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *layoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// layoutableStack.children = @[_layoutablePropertiesSectionTitle, horizontalStack, horizontalStack2, horizontalStack3, _alignSelfBtn]; -// layoutableStack.spacing = 5; -// layoutableStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *layoutSpecStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// layoutSpecStack.children = @[_layoutSpecPropertiesSectionTitle, _alignItemsBtn]; -// layoutSpecStack.spacing = 5; -// layoutSpecStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *debugHelpStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// debugHelpStack.children = @[_debugSectionTitle, _vizNodeInsetSizeBtn, _vizNodeBordersBtn]; -// debugHelpStack.spacing = 5; -// debugHelpStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *verticalLayoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// verticalLayoutableStack.style.flexGrow = 1.0; -// verticalLayoutableStack.spacing = 20; -// verticalLayoutableStack.children = @[_parentNodeNavBtn, horizontalStackNav, _childNodeNavBtn, itemDescriptionStack, layoutableStack, layoutSpecStack, debugHelpStack]; -// verticalLayoutableStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space -// -// ASLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(100, 10, 10, 10) child:verticalLayoutableStack]; -// insetSpec.style.flexGrow = 1.0; -// return insetSpec; -//} -// -//#pragma mark - configure Inspector node for layoutable -//- (void)updateInspectorWithLayoutable -//{ -// _itemDescription.attributedText = [self attributedStringFromLayoutable:_layoutElementToEdit]; -// -// if ([self node]) { -// UIColor *nodeBackgroundColor = [[self node] backgroundColor]; -// UIImage *colorBtnImg = [ASLayoutElementInspectorNode imageForButtonWithBackgroundColor:nodeBackgroundColor -// borderColor:[UIColor whiteColor] -// borderWidth:3]; -// [_itemBackgroundColorBtn setBackgroundImage:colorBtnImg forState:ASControlStateNormal]; -// } else { -// _itemBackgroundColorBtn.enabled = NO; -// } -// -// _flexGrowBtn.selected = [self.layoutElementToEdit flexGrow]; -// _flexGrowValue.attributedText = [self attributedStringFromString: (_flexGrowBtn.selected) ? @"YES" : @"NO"]; -// -// _flexShrinkBtn.selected = self.layoutElementToEdit.style.flexShrink; -// _flexShrinkValue.attributedText = [self attributedStringFromString: (_flexShrinkBtn.selected) ? @"YES" : @"NO"]; -// -// // _flexBasisBtn.selected = self.layoutElementToEdit.style.flexShrink; -// // _flexBasisValue.attributedText = [self attributedStringFromString: (_flexBasisBtn.selected) ? @"YES" : @"NO"]; -// -// -// NSUInteger alignSelfValue = [self.layoutElementToEdit alignSelf]; -// NSString *newTitle = [@"alignSelf:" stringByAppendingString:[self alignSelfName:alignSelfValue]]; -// [_alignSelfBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal]; -// -// if ([self layoutSpec]) { -// _alignItemsBtn.enabled = YES; -//// NSUInteger alignItemsValue = [[self layoutSpec] alignItems]; -//// newTitle = [@"alignItems:" stringByAppendingString:[self alignSelfName:alignItemsValue]]; -//// [_alignItemsBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal]; -// } -// -// [self setNeedsLayout]; -//} - - -//- (void)enableInspectorNodesForLayoutable -//{ -// if ([self layoutSpec]) { -// -// _itemBackgroundColorBtn.enabled = YES; -// _flexGrowBtn.enabled = YES; -// _flexShrinkBtn.enabled = YES; -// _flexBasisBtn.enabled = YES; -// _alignSelfBtn.enabled = YES; -// _spacingBeforeBtn.enabled = YES; -// _spacingAfterBtn.enabled = YES; -// _alignItemsBtn.enabled = YES; -// -// } else if ([self node]) { -// -// _itemBackgroundColorBtn.enabled = YES; -// _flexGrowBtn.enabled = YES; -// _flexShrinkBtn.enabled = YES; -// _flexBasisBtn.enabled = YES; -// _alignSelfBtn.enabled = YES; -// _spacingBeforeBtn.enabled = YES; -// _spacingAfterBtn.enabled = YES; -// _alignItemsBtn.enabled = NO; -// -// } else { -// -// _itemBackgroundColorBtn.enabled = NO; -// _flexGrowBtn.enabled = NO; -// _flexShrinkBtn.enabled = NO; -// _flexBasisBtn.enabled = NO; -// _alignSelfBtn.enabled = NO; -// _spacingBeforeBtn.enabled = NO; -// _spacingAfterBtn.enabled = NO; -// _alignItemsBtn.enabled = YES; -// } -//} - -//+ (NSDictionary *)alignSelfTypeNames -//{ -// return @{@(ASStackLayoutAlignSelfAuto) : @"Auto", -// @(ASStackLayoutAlignSelfStart) : @"Start", -// @(ASStackLayoutAlignSelfEnd) : @"End", -// @(ASStackLayoutAlignSelfCenter) : @"Center", -// @(ASStackLayoutAlignSelfStretch) : @"Stretch"}; -//} -// -//- (NSString *)alignSelfName:(NSUInteger)type -//{ -// return [[self class] alignSelfTypeNames][@(type)]; -//} -// -//+ (NSDictionary *)alignItemTypeNames -//{ -// return @{@(ASStackLayoutAlignItemsBaselineFirst) : @"BaselineFirst", -// @(ASStackLayoutAlignItemsBaselineLast) : @"BaselineLast", -// @(ASStackLayoutAlignItemsCenter) : @"Center", -// @(ASStackLayoutAlignItemsEnd) : @"End", -// @(ASStackLayoutAlignItemsStart) : @"Start", -// @(ASStackLayoutAlignItemsStretch) : @"Stretch"}; -//} -// -//- (NSString *)alignItemName:(NSUInteger)type -//{ -// return [[self class] alignItemTypeNames][@(type)]; -//} - -//#pragma mark - gesture handling -//- (void)changeColor:(ASButtonNode *)sender -//{ -// if ([self node]) { -// NSArray *colorArray = @[[UIColor orangeColor], -// [UIColor redColor], -// [UIColor greenColor], -// [UIColor purpleColor]]; -// -// UIColor *nodeBackgroundColor = [(ASDisplayNode *)self.layoutElementToEdit backgroundColor]; -// -// NSUInteger colorIndex = [colorArray indexOfObject:nodeBackgroundColor]; -// colorIndex = (colorIndex + 1 < [colorArray count]) ? colorIndex + 1 : 0; -// -// [[self node] setBackgroundColor: [colorArray objectAtIndex:colorIndex]]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setFlexGrowValue:(ASButtonNode *)sender -//{ -// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, -// -// if ([self layoutSpec]) { -// [[self layoutSpec] setFlexGrow:sender.isSelected]; -// } else if ([self node]) { -// [[self node] setFlexGrow:sender.isSelected]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setFlexShrinkValue:(ASButtonNode *)sender -//{ -// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, -// -// if ([self layoutSpec]) { -// [[self layoutSpec] setFlexShrink:sender.isSelected]; -// } else if ([self node]) { -// [[self node] setFlexShrink:sender.isSelected]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setAlignSelfValue:(ASButtonNode *)sender -//{ -// NSUInteger currentAlignSelfValue; -// NSUInteger nextAlignSelfValue; -// -// if ([self layoutSpec]) { -// currentAlignSelfValue = [[self layoutSpec] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self layoutSpec] setAlignSelf:nextAlignSelfValue]; -// -// } else if ([self node]) { -// currentAlignSelfValue = [[self node] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self node] setAlignSelf:nextAlignSelfValue]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setAlignItemsValue:(ASButtonNode *)sender -//{ -// NSUInteger currentAlignItemsValue; -// NSUInteger nextAlignItemsValue; -// -// if ([self layoutSpec]) { -// currentAlignItemsValue = [[self layoutSpec] alignSelf]; -// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0; -//// [[self layoutSpec] setAlignItems:nextAlignItemsValue]; -// -// } else if ([self node]) { -// currentAlignItemsValue = [[self node] alignSelf]; -// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0; -//// [[self node] setAlignItems:nextAlignItemsValue]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -//- (void)setFlexBasisValue:(ASButtonNode *)sender -//{ -// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, -// FIXME: finish -//} -// -//- (void)setVizNodeInsets:(ASButtonNode *)sender -//{ -// BOOL newState = !sender.selected; -// -// if (newState == YES) { -// self.vizNodeInsetSize = 0; -// [self.delegate toggleVisualization:NO]; // FIXME -// [self.delegate toggleVisualization:YES]; // FIXME -// _vizNodeBordersBtn.selected = YES; -// -// } else { -// self.vizNodeInsetSize = 10; -// [self.delegate toggleVisualization:NO]; // FIXME -// [self.delegate toggleVisualization:YES]; // FIXME -// } -// -// sender.selected = newState; -//} -// -//- (void)setVizNodeBorders:(ASButtonNode *)sender -//{ -// BOOL newState = !sender.selected; -// -// [self.delegate toggleVisualization:newState]; // FIXME -// -// sender.selected = newState; -//} - -@end diff --git a/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.h b/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.h deleted file mode 100644 index a6fc0efa02..0000000000 --- a/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// ASLayoutSpec+Debug.h -// AsyncDisplayKit -// -// Created by Hannah Troisi on 3/20/16. -// -// - -#pragma once -#import "ASControlNode.h" - -@class ASLayoutSpec; - -@interface ASLayoutSpecVisualizerNode : ASControlNode - -@property (nonatomic, strong) ASLayoutSpec *layoutSpec; - -- (instancetype)initWithLayoutSpec:(ASLayoutSpec *)layoutSpec; - -@end - diff --git a/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.m b/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.m deleted file mode 100644 index 78549bd636..0000000000 --- a/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.m +++ /dev/null @@ -1,73 +0,0 @@ -// -// ASLayoutSpec+Debug.m -// AsyncDisplayKit -// -// Created by Hannah Troisi on 3/20/16. -// -// - -#import "ASLayoutSpec+Debug.h" -#import "ASDisplayNode+Beta.h" -#import "AsyncDisplayKit.h" -#import "ASLayoutElementInspectorNode.h" - -@implementation ASLayoutSpecVisualizerNode - -- (instancetype)initWithLayoutSpec:(ASLayoutSpec *)layoutSpec -{ - self = [super init]; - if (self) { - self.borderWidth = 2; - self.borderColor = [[UIColor redColor] CGColor]; - self.layoutSpec = layoutSpec; - self.layoutSpec.neverShouldVisualize = YES; - self.automaticallyManagesSubnodes = YES; - self.shouldCacheLayoutSpec = YES; - [self addTarget:self action:@selector(visualizerNodeTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; - } - return self; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - CGFloat insetFloat = [ASLayoutElementInspectorNode sharedInstance].vizNodeInsetSize; - UIEdgeInsets insets = UIEdgeInsetsMake(insetFloat, insetFloat, insetFloat, insetFloat); - - // FIXME in framework: auto pass properties to children - ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:self.layoutSpec]; - insetSpec.neverShouldVisualize = YES; - - // propogate child's layoutSpec properties to the inset that we are adding -// insetSpec.style.flexGrow = _layoutSpec.style.flexGrow; -// insetSpec.style.flexShrink = _layoutSpec.style.flexShrink; -// insetSpec.alignSelf = _layoutSpec.alignSelf; - -// NSLog(@"%@: vizNode = %f, child = %f", self, insetSpec.style.flexGrow, _layoutSpec.style.flexGrow); - - return insetSpec; -} - -- (void)setLayoutSpec:(ASLayoutSpec *)layoutSpec // FIXME: this is duplicated in InspectorNode - make it a category on ASLayoutSpec? -{ - _layoutSpec = layoutSpec; // FIXME: should copy layoutSpec properities to self? - - if ([layoutSpec isKindOfClass:[ASInsetLayoutSpec class]]) { - self.borderColor = [[UIColor redColor] CGColor]; - - } else if ([layoutSpec isKindOfClass:[ASStackLayoutSpec class]]) { - self.borderColor = [[UIColor greenColor] CGColor]; - } -} - -- (NSString *)description -{ - return [self.layoutSpec description]; // FIXME: expand on layoutSpec description (e.g. have StackLayoutSpec return horz/vert) -} - -- (void)visualizerNodeTapped:(UIGestureRecognizer *)sender -{ - [[ASLayoutElementInspectorNode sharedInstance] setLayoutElementToEdit:self.layoutSpec]; -} - -@end - diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.h b/AsyncDisplayKit/Details/ASAbstractLayoutController.h deleted file mode 100644 index bcbbd89d01..0000000000 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// ASAbstractLayoutController.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASAbstractLayoutController : NSObject - -@end - -@interface ASAbstractLayoutController (Unavailable) - -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType __unavailable; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.h b/AsyncDisplayKit/Details/ASChangeSetDataController.h deleted file mode 100644 index fa24b28ae2..0000000000 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// ASChangeSetDataController.h -// AsyncDisplayKit -// -// Created by Huy Nguyen on 19/10/15. -// -// 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 - -/** - * @abstract Subclass of ASDataController that simulates ordering of operations in batch updates defined in UITableView and UICollectionView. - * - * @discussion The ordering is achieved by using _ASHierarchyChangeSet to enqueue and sort operations. - * More information about the ordering and the index paths used for operations can be found here: - * https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/ManageInsertDeleteRow/ManageInsertDeleteRow.html#//apple_ref/doc/uid/TP40007451-CH10-SW17 - * - * @see ASDataController - * @see _ASHierarchyChangeSet - */ -@interface ASChangeSetDataController : ASDataController - -@end diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.mm b/AsyncDisplayKit/Details/ASChangeSetDataController.mm deleted file mode 100644 index 10ae0f28d0..0000000000 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.mm +++ /dev/null @@ -1,195 +0,0 @@ -// -// ASChangeSetDataController.m -// AsyncDisplayKit -// -// Created by Huy Nguyen on 19/10/15. -// -// 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 "ASChangeSetDataController.h" -#import "_ASHierarchyChangeSet.h" -#import "ASAssert.h" -#import "ASDataController+Subclasses.h" - -@implementation ASChangeSetDataController { - NSInteger _changeSetBatchUpdateCounter; - _ASHierarchyChangeSet *_changeSet; -} - -- (void)dealloc -{ - ASDisplayNodeCAssert(_changeSetBatchUpdateCounter == 0, @"ASChangeSetDataController deallocated in the middle of a batch update."); -} - -#pragma mark - Batching (External API) - -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - if (_changeSetBatchUpdateCounter <= 0) { - _changeSetBatchUpdateCounter = 0; - _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[self itemCountsFromDataSource]]; - } - _changeSetBatchUpdateCounter++; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - _changeSetBatchUpdateCounter--; - - // Prevent calling endUpdatesAnimated:completion: in an unbalanced way - NSAssert(_changeSetBatchUpdateCounter >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); - - [_changeSet addCompletionHandler:completion]; - if (_changeSetBatchUpdateCounter == 0) { - void (^batchCompletion)(BOOL) = _changeSet.completionHandler; - - /** - * If the initial reloadData has not been called, just bail because we don't have - * our old data source counts. - * See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable - * For the issue that UICollectionView has that we're choosing to workaround. - */ - if (!self.initialReloadDataHasBeenCalled) { - if (batchCompletion != nil) { - batchCompletion(YES); - } - _changeSet = nil; - return; - } - - [self invalidateDataSourceItemCounts]; - [_changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]]; - - ASDataControllerLogEvent(self, @"triggeredUpdate: %@", _changeSet); - - [super beginUpdates]; - - for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { - [super deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - [super deleteSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [super insertSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [super insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - -#if ASEVENTLOG_ENABLE - NSString *changeSetDescription = ASObjectDescriptionMakeTiny(_changeSet); - batchCompletion = ^(BOOL finished) { - if (batchCompletion != nil) { - batchCompletion(finished); - } - ASDataControllerLogEvent(self, @"finishedUpdate: %@", changeSetDescription); - }; -#endif - - [super endUpdatesAnimated:animated completion:batchCompletion]; - - _changeSet = nil; - } -} - -- (BOOL)batchUpdating -{ - BOOL batchUpdating = (_changeSetBatchUpdateCounter != 0); - // _changeSet must be available during batch update - ASDisplayNodeAssertTrue(batchUpdating == (_changeSet != nil)); - return batchUpdating; -} - -- (void)waitUntilAllUpdatesAreCommitted -{ - ASDisplayNodeAssertMainThread(); - if (self.batchUpdating) { - // This assertion will be enabled soon. -// ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); - return; - } - - [super waitUntilAllUpdatesAreCommitted]; -} - -#pragma mark - Section Editing (External API) - -- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet insertSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet reloadSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; - [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; - [self endUpdates]; -} - -#pragma mark - Row Editing (External API) - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet insertItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet reloadItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; - [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; - [self endUpdates]; -} - -@end diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h deleted file mode 100644 index 74c08ec413..0000000000 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// ASCollectionDataController.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import - -#import -#import - -@class ASDisplayNode; -@class ASCollectionDataController; -@protocol ASDataControllerSource; -@protocol ASSectionContext; - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASCollectionDataControllerSource - -/** - The constrained size range for layout. - */ -- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController; - -- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; - -- (nullable id)dataController:(ASCollectionDataController *)dataController contextForSection:(NSInteger)section; - -@optional - -- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -- (ASCellNodeBlock)dataController:(ASCollectionDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -@end - -@interface ASCollectionDataController : ASChangeSetDataController - -- (instancetype)initWithDataSource:(id)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; - -- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -- (nullable id)contextForSection:(NSInteger)section; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm deleted file mode 100644 index bdbdd9801f..0000000000 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ /dev/null @@ -1,342 +0,0 @@ -// -// ASCollectionDataController.mm -// AsyncDisplayKit -// -// 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 "ASCollectionDataController.h" - -#import "ASAssert.h" -#import "ASMultidimensionalArrayUtils.h" -#import "ASCellNode.h" -#import "ASDataController+Subclasses.h" -#import "ASIndexedNodeContext.h" -#import "ASSection.h" -#import "ASSectionContext.h" - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -@interface ASCollectionDataController () { - BOOL _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath; - NSInteger _nextSectionID; - NSMutableArray *_sections; - NSArray *_pendingSections; -} - -- (id)collectionDataSource; - -@end - -@implementation ASCollectionDataController { - NSMutableDictionary *> *_pendingNodeContexts; -} - -- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog -{ - self = [super initWithDataSource:dataSource eventLog:eventLog]; - if (self != nil) { - _pendingNodeContexts = [NSMutableDictionary dictionary]; - _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; - - ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [dataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]); - - _nextSectionID = 0; - _sections = [NSMutableArray array]; - } - return self; -} - -- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - ASDisplayNodeAssertMainThread(); - NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)]; - - [_sections removeAllObjects]; - [self _populatePendingSectionsFromDataSource:sections]; - - for (NSString *kind in [self supplementaryKinds]) { - LOG(@"Populating elements of kind: %@", kind); - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)]; - - [self applyPendingSections:sectionIndexes]; - - [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, __unused BOOL * _Nonnull stop) { - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - - NSArray *editingNodes = [self editingNodesOfKind:kind]; - NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; - [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; - - // Insert each section - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:newSectionCount]; - for (int i = 0; i < newSectionCount; i++) { - [sections addObject:[NSMutableArray array]]; - } - [self insertSections:sections ofKind:kind atIndexSet:sectionIndexes completion:nil]; - - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - }]; - [_pendingNodeContexts removeAllObjects]; -} - -- (void)prepareForInsertSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - [self _populatePendingSectionsFromDataSource:sections]; - - for (NSString *kind in [self supplementaryKinds]) { - LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections); - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willInsertSections:(NSIndexSet *)sections -{ - [self applyPendingSections:sections]; - - [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) { - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; - for (NSUInteger i = 0; i < sections.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - - [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - }]; - [_pendingNodeContexts removeAllObjects]; -} - -- (void)willDeleteSections:(NSIndexSet *)sections -{ - [_sections removeObjectsAtIndexes:sections]; - - for (NSString *kind in [self supplementaryKinds]) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); - - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - [self deleteSectionsOfKind:kind atIndexSet:sections completion:nil]; - } -} - -- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - ASSection *movedSection = [_sections objectAtIndex:section]; - [_sections removeObjectAtIndex:section]; - [_sections insertObject:movedSection atIndex:newSection]; - - NSIndexSet *sectionAsIndexSet = [NSIndexSet indexSetWithIndex:section]; - for (NSString *kind in [self supplementaryKinds]) { - NSMutableArray *editingNodes = [self editingNodesOfKind:kind]; - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingNodes, sectionAsIndexSet); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - - // update the section of indexpaths - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - for (NSIndexPath *indexPath in indexPaths) { - NSUInteger newItem = [indexPath indexAtPosition:indexPath.length - 1]; - NSIndexPath *mappedIndexPath = [NSIndexPath indexPathForItem:newItem inSection:newSection]; - [updatedIndexPaths addObject:mappedIndexPath]; - } - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - } -} - -- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - for (NSString *kind in [self supplementaryKinds]) { - LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths); - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) { - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - }]; - - [_pendingNodeContexts removeAllObjects]; -} - -- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - for (NSString *kind in [self supplementaryKinds]) { - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - for (NSString *kind in [self supplementaryKinds]) { - NSArray *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths); - - [self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil]; - - // If any of the contexts remain after the deletion, re-insert them, e.g. - // UICollectionElementKindSectionHeader remains even if item 0 is deleted. - NSMutableArray *reinsertedContexts = [NSMutableArray array]; - for (ASIndexedNodeContext *context in _pendingNodeContexts[kind]) { - if ([deletedIndexPaths containsObject:context.indexPath]) { - [reinsertedContexts addObject:context]; - } - } - - [self batchLayoutNodesFromContexts:reinsertedContexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - } - [_pendingNodeContexts removeAllObjects]; -} - -- (void)_populatePendingSectionsFromDataSource:(NSIndexSet *)sectionIndexes -{ - ASDisplayNodeAssertMainThread(); - - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionIndexes.count]; - [sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - id context = [self.collectionDataSource dataController:self contextForSection:idx]; - [sections addObject:[[ASSection alloc] initWithSectionID:_nextSectionID context:context]]; - _nextSectionID++; - }]; - _pendingSections = sections; -} - -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray *)contexts -{ - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) { - NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; - for (NSUInteger i = 0; i < itemCount; i++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; - [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment]; - } - } - }]; -} - -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths mutableContexts:(NSMutableArray *)contexts -{ - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - NSMutableIndexSet *sections = [NSMutableIndexSet indexSet]; - for (NSIndexPath *indexPath in indexPaths) { - [sections addIndex:indexPath.section]; - } - - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) { - NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; - for (NSUInteger i = 0; i < itemCount; i++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; - [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment]; - } - } - }]; -} - -- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray *)contexts environment:(id)environment -{ - ASCellNodeBlock supplementaryCellBlock; - if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { - supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; - } else { - ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; - supplementaryCellBlock = ^{ return supplementaryNode; }; - } - - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock - indexPath:indexPath - supplementaryElementKind:kind - constrainedSize:constrainedSize - environment:environment]; - [contexts addObject:context]; -} - -#pragma mark - Sizing query - -- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - if ([kind isEqualToString:ASDataControllerRowNodeKind]) { - return [super constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - } else { - ASDisplayNodeAssertMainThread(); - return [self.collectionDataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; - } -} - -#pragma mark - External supplementary store and section context querying - -- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - NSArray *nodesOfKind = [self completedNodesOfKind:kind]; - NSInteger section = indexPath.section; - if (section < nodesOfKind.count) { - NSArray *nodesOfKindInSection = nodesOfKind[section]; - NSInteger itemIndex = indexPath.item; - if (itemIndex < nodesOfKindInSection.count) { - return nodesOfKindInSection[itemIndex]; - } - } - return nil; -} - -- (id)contextForSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssertTrue(section >= 0 && section < _sections.count); - return _sections[section].context; -} - -#pragma mark - Private Helpers - -- (NSArray *)supplementaryKinds -{ - return [self.collectionDataSource supplementaryNodeKindsInDataController:self]; -} - -- (id)collectionDataSource -{ - return (id)self.dataSource; -} - -- (void)applyPendingSections:(NSIndexSet *)sectionIndexes -{ - [_sections insertObjects:_pendingSections atIndexes:sectionIndexes]; - _pendingSections = nil; -} - -@end diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm deleted file mode 100644 index d2db393f25..0000000000 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm +++ /dev/null @@ -1,77 +0,0 @@ -// -// ASCollectionViewLayoutController.mm -// AsyncDisplayKit -// -// 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 "ASCollectionViewLayoutController.h" - -#import "ASAssert.h" -#import "ASCollectionView.h" -#import "CoreGraphics+ASConvenience.h" -#import "UICollectionViewLayout+ASConvenience.h" - -struct ASRangeGeometry { - CGRect rangeBounds; - CGRect updateBounds; -}; -typedef struct ASRangeGeometry ASRangeGeometry; - - -#pragma mark - -#pragma mark ASCollectionViewLayoutController - -@interface ASCollectionViewLayoutController () -{ - @package - ASCollectionView * __weak _collectionView; - UICollectionViewLayout * __strong _collectionViewLayout; -} -@end - -@implementation ASCollectionViewLayoutController - -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView -{ - if (!(self = [super init])) { - return nil; - } - - _collectionView = collectionView; - _collectionViewLayout = [collectionView collectionViewLayout]; - return self; -} - -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - CGRect rangeBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:tuningParameters]; - return [self indexPathsForItemsWithinRangeBounds:rangeBounds]; -} - -- (NSSet *)indexPathsForItemsWithinRangeBounds:(CGRect)rangeBounds -{ - NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds]; - NSMutableSet *indexPathSet = [NSMutableSet setWithCapacity:layoutAttributes.count]; - - for (UICollectionViewLayoutAttributes *la in layoutAttributes) { - //ASDisplayNodeAssert(![indexPathSet containsObject:la.indexPath], @"Shouldn't already contain indexPath"); - [indexPathSet addObject:la.indexPath]; - } - - return indexPathSet; -} - -- (CGRect)rangeBoundsWithScrollDirection:(ASScrollDirection)scrollDirection - rangeTuningParameters:(ASRangeTuningParameters)tuningParameters -{ - CGRect rect = _collectionView.bounds; - - return CGRectExpandToRangeWithScrollableDirections(rect, tuningParameters, [_collectionView scrollableDirections], scrollDirection); -} - -@end diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h deleted file mode 100644 index 00af36fde6..0000000000 --- a/AsyncDisplayKit/Details/ASDataController.h +++ /dev/null @@ -1,231 +0,0 @@ -// -// ASDataController.h -// AsyncDisplayKit -// -// 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. -// - -#pragma once - -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#if ASEVENTLOG_ENABLE -#define ASDataControllerLogEvent(dataController, ...) [dataController.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__] -#else -#define ASDataControllerLogEvent(dataController, ...) -#endif - -@class ASCellNode; -@class ASDataController; -@protocol ASEnvironment; - -typedef NSUInteger ASDataControllerAnimationOptions; - -/** - * ASCellNode creation block. Used to lazily create the ASCellNode instance for a specified indexPath. - */ -typedef ASCellNode * _Nonnull(^ASCellNodeBlock)(); - -FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; - -/** - Data source for data controller - It will be invoked in the same thread as the api call of ASDataController. - */ - -@protocol ASDataControllerSource - -/** - Fetch the ASCellNode block for specific index path. This block should return the ASCellNode for the specified index path. - */ -- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath; - -/** - The constrained size range for layout. - */ -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; - -/** - Fetch the number of rows in specific section. - */ -- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section; - -/** - Fetch the number of sections. - */ -- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController; - -@end - -@protocol ASDataControllerEnvironmentDelegate -- (id)dataControllerEnvironment; -@end - -/** - Delegate for notify the data updating of data controller. - These methods will be invoked from main thread right now, but it may be moved to background thread in the future. - */ -@protocol ASDataControllerDelegate - -@optional - -/** - Called for batch update. - */ -- (void)dataControllerBeginUpdates:(ASDataController *)dataController; -- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL))completion; - -/** - Called for insertion of elements. - */ -- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - Called for deletion of elements. - */ -- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - Called for insertion of sections. - */ -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *> *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - Called for deletion of sections. - */ -- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -@end - -/** - * Controller to layout data in background, and managed data updating. - * - * All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the data - * will be updated asynchronously. The dataSource must be updated to reflect the changes before these methods has been called. - * For each data updating, the corresponding methods in delegate will be called. - */ -@protocol ASFlowLayoutControllerDataSource; -@interface ASDataController : NSObject - -- (instancetype)initWithDataSource:(id)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; - -/** - Data source for fetching data info. - */ -@property (nonatomic, weak, readonly) id dataSource; - -/** - Delegate to notify when data is updated. - */ -@property (nonatomic, weak) id delegate; - -/** - * - */ -@property (nonatomic, weak) id environmentDelegate; - -/** - * Returns YES if reloadData has been called at least once. Before this point it is - * important to ignore/suppress some operations. For example, inserting a section - * before the initial data load should have no effect. - * - * This must be called on the main thread. - */ -@property (nonatomic, readonly) BOOL initialReloadDataHasBeenCalled; - -#if ASEVENTLOG_ENABLE -/* - * @abstract The primitive event tracing object. You shouldn't directly use it to log event. Use the ASDataControllerLogEvent macro instead. - */ -@property (nonatomic, strong, readonly) ASEventLog *eventLog; -#endif - -/** @name Data Updating */ - -- (void)beginUpdates; - -- (void)endUpdates; - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL))completion; - -- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - * Re-measures all loaded nodes in the backing store. - * - * @discussion Used to respond to a change in size of the containing view - * (e.g. ASTableView or ASCollectionView after an orientation change). - */ -- (void)relayoutAllNodes; - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^ _Nullable)())completion; - -- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)waitUntilAllUpdatesAreCommitted; - -/** @name Data Querying */ - -- (NSUInteger)numberOfSections; - -- (NSUInteger)numberOfRowsInSection:(NSUInteger)section; - -- (nullable ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath; - -- (NSUInteger)completedNumberOfSections; - -- (NSUInteger)completedNumberOfRowsInSection:(NSUInteger)section; - -- (nullable ASCellNode *)nodeAtCompletedIndexPath:(NSIndexPath *)indexPath; - -/** - * @return The index path, in the data source's index space, for the given node. - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; - -/** - * @return The index path, in UIKit's index space, for the given node. - * - * @discussion @c indexPathForNode: is returns an index path in the data source's index space. - * This method is useful for e.g. looking up the cell for a given node. - */ -- (nullable NSIndexPath *)completedIndexPathForNode:(ASCellNode *)cellNode; - -/** - * Direct access to the nodes that have completed calculation and layout - */ -- (NSArray *> *)completedNodes; - -/** - * Immediately move this item. This is called by ASTableView when the user has finished an interactive - * item move and the table view is requesting a model update. - * - * This must be called on the main thread. - */ -- (void)moveCompletedNodeAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm deleted file mode 100644 index db4a499b2d..0000000000 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ /dev/null @@ -1,1084 +0,0 @@ -// -// ASDataController.mm -// AsyncDisplayKit -// -// 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 "ASDataController.h" - -#import "ASAssert.h" -#import "ASCellNode.h" -#import "ASEnvironmentInternal.h" -#import "ASLayout.h" -#import "ASMainSerialQueue.h" -#import "ASMultidimensionalArrayUtils.h" -#import "ASThread.h" -#import "ASIndexedNodeContext.h" -#import "ASDataController+Subclasses.h" -#import "ASDispatch.h" -#import "ASInternalHelpers.h" -#import "ASCellNode+Internal.h" - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -#define AS_MEASURE_AVOIDED_DATACONTROLLER_WORK 0 - -#define RETURN_IF_NO_DATASOURCE(val) if (_dataSource == nil) { return val; } -#define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd)) - -const static NSUInteger kASDataControllerSizingCountPerProcessor = 5; -const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey"; -const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext"; - -NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK -@interface ASDataController (AvoidedWorkMeasuring) -+ (void)_didLayoutNode; -+ (void)_expectToInsertNodes:(NSUInteger)count; -@end -#endif - -@interface ASDataController () { - NSMutableDictionary *_nodeContexts; // Main thread only. This is modified immediately during edits i.e. these are in the dataSource's index space. - NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. - NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. - NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propagated to _completedNodes. - BOOL _itemCountsFromDataSourceAreValid; // Main thread only. - std::vector _itemCountsFromDataSource; // Main thread only. - - ASMainSerialQueue *_mainSerialQueue; - - dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. - dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting. - - BOOL _initialReloadDataHasBeenCalled; - - BOOL _delegateDidInsertNodes; - BOOL _delegateDidDeleteNodes; - BOOL _delegateDidInsertSections; - BOOL _delegateDidDeleteSections; -} - -@end - -@implementation ASDataController - -#pragma mark - Lifecycle - -- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog -{ - if (!(self = [super init])) { - return nil; - } - ASDisplayNodeAssert(![self isMemberOfClass:[ASDataController class]], @"ASDataController is an abstract class and should not be instantiated. Instantiate a subclass instead."); - - _dataSource = dataSource; - -#if ASEVENTLOG_ENABLE - _eventLog = eventLog; -#endif - - _nodeContexts = [NSMutableDictionary dictionary]; - _completedNodes = [NSMutableDictionary dictionary]; - _editingNodes = [NSMutableDictionary dictionary]; - - _nodeContexts[ASDataControllerRowNodeKind] = [NSMutableArray array]; - _completedNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; - _editingNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; - - _mainSerialQueue = [[ASMainSerialQueue alloc] init]; - - const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding]; - _editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); - dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL); - _editingTransactionGroup = dispatch_group_create(); - - return self; -} - -- (instancetype)init -{ - ASDisplayNodeFailAssert(@"Failed to call designated initializer."); - id fakeDataSource = nil; - ASEventLog *eventLog = nil; - return [self initWithDataSource:fakeDataSource eventLog:eventLog]; -} - -- (void)setDelegate:(id)delegate -{ - if (_delegate == delegate) { - return; - } - - _delegate = delegate; - - // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later. - _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)]; - _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; -} - -+ (NSUInteger)parallelProcessorCount -{ - static NSUInteger parallelProcessorCount; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - parallelProcessorCount = [[NSProcessInfo processInfo] activeProcessorCount]; - }); - - return parallelProcessorCount; -} - -#pragma mark - Cell Layout - -- (void)batchLayoutNodesFromContexts:(NSArray *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler -{ - ASSERT_ON_EDITING_QUEUE; -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK - [ASDataController _expectToInsertNodes:contexts.count]; -#endif - - if (contexts.count == 0) { - batchCompletionHandler(@[], @[]); - return; - } - - ASProfilingSignpostStart(2, _dataSource); - - NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; - NSUInteger count = contexts.count; - - // Processing in batches - for (NSUInteger i = 0; i < count; i += blockSize) { - NSRange batchedRange = NSMakeRange(i, MIN(count - i, blockSize)); - NSArray *batchedContexts = [contexts subarrayWithRange:batchedRange]; - NSArray *nodes = [self _layoutNodesFromContexts:batchedContexts]; - NSArray *indexPaths = [ASIndexedNodeContext indexPathsFromContexts:batchedContexts]; - batchCompletionHandler(nodes, indexPaths); - } - - ASProfilingSignpostEnd(2, _dataSource); -} - -/** - * Measure and layout the given node with the constrained size range. - */ -- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize -{ - CGRect frame = CGRectZero; - frame.size = [node layoutThatFits:constrainedSize].size; - node.frame = frame; -} - -/** - * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. - */ -- (void)_batchLayoutAndInsertNodesFromContexts:(NSArray *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - // Insert finished nodes into data storage - [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - -- (NSArray *)_layoutNodesFromContexts:(NSArray *)contexts -{ - ASSERT_ON_EDITING_QUEUE; - - NSUInteger nodeCount = contexts.count; - if (!nodeCount || _dataSource == nil) { - return nil; - } - - __strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(nodeCount, sizeof(ASCellNode *)); - - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - ASDispatchApply(nodeCount, queue, 0, ^(size_t i) { - RETURN_IF_NO_DATASOURCE(); - - // Allocate the node. - ASIndexedNodeContext *context = contexts[i]; - ASCellNode *node = context.node; - if (node == nil) { - ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); - node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. - } - - [self _layoutNode:node withConstrainedSize:context.constrainedSize]; -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK - [ASDataController _didLayoutNode]; -#endif - allocatedNodeBuffer[i] = node; - }); - - BOOL canceled = _dataSource == nil; - - // Create nodes array - NSArray *nodes = canceled ? nil : [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount]; - - // Nil out buffer indexes to allow arc to free the stored cells. - for (int i = 0; i < nodeCount; i++) { - allocatedNodeBuffer[i] = nil; - } - free(allocatedNodeBuffer); - - return nodes; -} - -- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - return [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]; -} - -#pragma mark - External Data Querying + Editing - -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexPaths.count || _dataSource == nil) { - return; - } - - NSMutableArray *editingNodes = _editingNodes[kind]; - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSMutableArray *completedNodes = ASTwoDimensionalArrayDeepMutableCopy(editingNodes); - - [_mainSerialQueue performBlockOnMainThread:^{ - _completedNodes[kind] = completedNodes; - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; -} - -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexPaths.count || _dataSource == nil) { - return; - } - - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForTwoDimensionalArray(_editingNodes[kind])); - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); - - [_mainSerialQueue performBlockOnMainThread:^{ - NSMutableArray *allNodes = _completedNodes[kind]; - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(allNodes, indexPaths); - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(allNodes, indexPaths); - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; -} - -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexSet.count|| _dataSource == nil) { - return; - } - - if (_editingNodes[kind] == nil) { - _editingNodes[kind] = [NSMutableArray array]; - } - - [_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSArray *sectionsForCompleted = ASTwoDimensionalArrayDeepMutableCopy(sections); - - [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; - if (completionBlock) { - completionBlock(sections, indexSet); - } - }]; -} - -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexSet.count || _dataSource == nil) { - return; - } - - [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; - [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; - if (completionBlock) { - completionBlock(indexSet); - } - }]; -} - -#pragma mark - Internal Data Querying + Editing - -/** - * Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes. - * - * @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy - * of the editing nodes. The delegate is invoked on the main thread. - */ -- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidInsertNodes) - [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - -/** - * Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed. - * - * @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread. - * Once the backing stores are consistent, the delegate is invoked on the main thread. - */ -- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidDeleteNodes) - [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - -/** - * Inserts sections, represented as arrays, into the backing store at the given indices and notifies the delegate. - * - * @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted - * in the completed store on the main thread. The delegate is invoked on the main thread. - */ -- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidInsertSections) - [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -/** - * Removes sections at the given indices from the backing store and notifies the delegate. - * - * @discussion Section array are first removed from the editing store, then the associated section in the completed - * store is removed on the main thread. The delegate is invoked on the main thread. - */ -- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidDeleteSections) - [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -#pragma mark - Initial Load & Full Reload (External API) - -- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion]; -} - -- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil]; -} - -- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion -{ - ASDisplayNodeAssertMainThread(); - - _initialReloadDataHasBeenCalled = YES; - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - [self invalidateDataSourceItemCounts]; - NSUInteger sectionCount = [self itemCountsFromDataSource].size(); - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - NSArray *newContexts = [self _populateNodeContextsFromDataSourceForSections:sectionIndexes]; - - // Update _nodeContexts - NSMutableArray *allContexts = _nodeContexts[ASDataControllerRowNodeKind]; - [allContexts removeAllObjects]; - NSArray *nodeIndexPaths = [ASIndexedNodeContext indexPathsFromContexts:newContexts]; - for (int i = 0; i < sectionCount; i++) { - [allContexts addObject:[[NSMutableArray alloc] init]]; - } - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(allContexts, nodeIndexPaths, newContexts); - - // Allow subclasses to perform setup before going into the edit transaction - [self prepareForReloadDataWithSectionCount:sectionCount]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - LOG(@"Edit Transaction - reloadData"); - - /** - * Leave the current data in the collection view until the first batch of nodes are laid out. - * Once the first batch is laid out, in one operation, replace all the sections and insert - * the first batch of items. - * - * We previously would replace all the sections immediately, and then start adding items as they - * were laid out. This resulted in more traffic to the UICollectionView and it also caused all the - * section headers to bunch up until the items come and fill out the sections. - */ - __block BOOL isFirstBatch = YES; - [self batchLayoutNodesFromContexts:newContexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - if (isFirstBatch) { - // -beginUpdates - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataControllerBeginUpdates:self]; - }]; - - // deleteSections: - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSUInteger oldSectionCount = [_editingNodes[ASDataControllerRowNodeKind] count]; - if (oldSectionCount) { - NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, oldSectionCount)]; - [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - } - - [self willReloadDataWithSectionCount:sectionCount]; - - // insertSections: - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; - for (int i = 0; i < sectionCount; i++) { - [sections addObject:[[NSMutableArray alloc] init]]; - } - [self _insertSections:sections atIndexSet:sectionIndexes withAnimationOptions:animationOptions]; - } - - // insertItemsAtIndexPaths: - [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - if (isFirstBatch) { - // -endUpdates - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataController:self endUpdatesAnimated:NO completion:nil]; - }]; - isFirstBatch = NO; - } - }]; - - if (completion) { - [_mainSerialQueue performBlockOnMainThread:completion]; - } - }); - if (synchronously) { - [self waitUntilAllUpdatesAreCommitted]; - } -} - -- (void)waitUntilAllUpdatesAreCommitted -{ - ASDisplayNodeAssertMainThread(); - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Schedule block in main serial queue to wait until all operations are finished that are - // where scheduled while waiting for the _editingTransactionQueue to finish - [_mainSerialQueue performBlockOnMainThread:^{ }]; -} - -#pragma mark - Data Source Access (Calling _dataSource) - -/** - * Fetches row contexts for the provided sections from the data source. - */ -- (NSArray *)_populateNodeContextsFromDataSourceForSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - std::vector counts = [self itemCountsFromDataSource]; - NSMutableArray *contexts = [NSMutableArray array]; - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { - NSUInteger itemCount = counts[sectionIndex]; - for (NSUInteger i = 0; i < itemCount; i++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIndex]; - ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; - - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; - [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock - indexPath:indexPath - supplementaryElementKind:nil - constrainedSize:constrainedSize - environment:environment]]; - } - } - }]; - return contexts; -} - -- (void)invalidateDataSourceItemCounts -{ - ASDisplayNodeAssertMainThread(); - _itemCountsFromDataSourceAreValid = NO; -} - -- (std::vector)itemCountsFromDataSource -{ - ASDisplayNodeAssertMainThread(); - if (NO == _itemCountsFromDataSourceAreValid) { - id source = self.dataSource; - NSInteger sectionCount = [source numberOfSectionsInDataController:self]; - std::vector newCounts; - newCounts.reserve(sectionCount); - for (NSInteger i = 0; i < sectionCount; i++) { - newCounts.push_back([source dataController:self rowsInSection:i]); - } - _itemCountsFromDataSource = newCounts; - _itemCountsFromDataSourceAreValid = YES; - } - return _itemCountsFromDataSource; -} - -#pragma mark - Batching (External API) - -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - // TODO: make this -waitUntilAllUpdatesAreCommitted? - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // Deep copy _completedNodes to _externalCompletedNodes. - // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. - _externalCompletedNodes = ASTwoDimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); - - LOG(@"beginUpdates - begin updates call to delegate"); - [_delegate dataControllerBeginUpdates:self]; - }]; - }); -} - -- (void)endUpdates -{ - [self endUpdatesAnimated:YES completion:nil]; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - LOG(@"endUpdatesWithCompletion - beginning"); - ASDisplayNodeAssertMainThread(); - - // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. - // Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction. - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // Now that the transaction is done, _completedNodes can be accessed externally again. - _externalCompletedNodes = nil; - - LOG(@"endUpdatesWithCompletion - calling delegate end"); - [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; - }]; - }); -} - -#pragma mark - Section Editing (External API) - -- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - insertSections: %@", sections); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - NSArray *contexts = [self _populateNodeContextsFromDataSourceForSections:sections]; - - // Update _nodeContexts - { - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; - for (NSUInteger i = 0; i < sections.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - NSMutableArray *allRowContexts = _nodeContexts[ASDataControllerRowNodeKind]; - [allRowContexts insertObjects:sectionArray atIndexes:sections]; - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(allRowContexts, [ASIndexedNodeContext indexPathsFromContexts:contexts], contexts); - } - - [self prepareForInsertSections:sections]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willInsertSections:sections]; - - LOG(@"Edit Transaction - insertSections: %@", sections); - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; - for (NSUInteger i = 0; i < sections.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - - [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - - [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }); -} - -- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - deleteSections: %@", sections); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - [_nodeContexts[ASDataControllerRowNodeKind] removeObjectsAtIndexes:sections]; - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willDeleteSections:sections]; - - // remove elements - LOG(@"Edit Transaction - deleteSections: %@", sections); - [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }); -} - -- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController the reload will be broken into delete & insert.", NSStringFromSelector(_cmd)); -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - moveSection"); - - if (!_initialReloadDataHasBeenCalled) { - return; - } - - NSMutableArray *rowContexts = _nodeContexts[ASDataControllerRowNodeKind]; - NSArray *contexts = rowContexts[section]; - [rowContexts removeObjectAtIndex:section]; - [rowContexts insertObject:contexts atIndex:section]; - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willMoveSection:section toSection:newSection]; - - // remove elements - - LOG(@"Edit Transaction - moveSection"); - NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind]; - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - // update the section of indexpaths - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - for (NSIndexPath *indexPath in indexPaths) { - NSIndexPath *updatedIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:newSection]; - [updatedIndexPaths addObject:updatedIndexPath]; - } - - // Don't re-calculate size for moving - [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - }); -} - - -#pragma mark - Backing store manipulation optional hooks (Subclass API) - -- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)prepareForInsertSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willInsertSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willDeleteSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -#pragma mark - Row Editing (External API) - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - LOG(@"Edit Command - insertRows: %@", indexPaths); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Sort indexPath to avoid messing up the index when inserting in several batches - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - for (NSIndexPath *indexPath in sortedIndexPaths) { - ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; - [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock - indexPath:indexPath - supplementaryElementKind:nil - constrainedSize:constrainedSize - environment:environment]]; - } - - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(_nodeContexts[ASDataControllerRowNodeKind], sortedIndexPaths, contexts); - [self prepareForInsertRowsAtIndexPaths:indexPaths]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willInsertRowsAtIndexPaths:indexPaths]; - - LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }); -} - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - - if (!_initialReloadDataHasBeenCalled) { - return; - } - - LOG(@"Edit Command - deleteRows: %@", indexPaths); - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Sort indexPath in order to avoid messing up the index when deleting in several batches. - // FIXME: Shouldn't deletes be sorted in descending order? - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_nodeContexts[ASDataControllerRowNodeKind], sortedIndexPaths); - [self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willDeleteRowsAtIndexPaths:sortedIndexPaths]; - - LOG(@"Edit Transaction - deleteRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; - }); -} - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController and the reload will be broken into delete & insert.", NSStringFromSelector(_cmd)); -} - -- (void)relayoutAllNodes -{ - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - LOG(@"Edit Command - relayoutRows"); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Can't relayout right away because _completedNodes may not be up-to-date, - // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes - // (see _layoutNodes:atIndexPaths:withAnimationOptions:). - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - for (NSString *kind in _completedNodes) { - [self _relayoutNodesOfKind:kind]; - } - }]; - }); -} - -- (void)_relayoutNodesOfKind:(NSString *)kind -{ - ASDisplayNodeAssertMainThread(); - NSArray *nodes = [self completedNodesOfKind:kind]; - if (!nodes.count) { - return; - } - - NSUInteger sectionIndex = 0; - for (NSMutableArray *section in nodes) { - NSUInteger rowIndex = 0; - for (ASCellNode *node in section) { - RETURN_IF_NO_DATASOURCE(); - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - [self _layoutNode:node withConstrainedSize:constrainedSize]; - rowIndex += 1; - } - sectionIndex += 1; - } -} - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - NSMutableArray *contexts = _nodeContexts[ASDataControllerRowNodeKind]; - ASIndexedNodeContext *context = contexts[indexPath.section][indexPath.item]; - [contexts[indexPath.section] removeObjectAtIndex:indexPath.item]; - [contexts[newIndexPath.section] insertObject:context atIndex:newIndexPath.item]; - - LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); - NSArray *indexPaths = @[indexPath]; - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - // Don't re-calculate size for moving - NSArray *newIndexPaths = @[newIndexPath]; - [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; - }); -} - -#pragma mark - Data Querying (Subclass API) - -- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind -{ - NSArray *nodes = _editingNodes[kind]; - return nodes != nil ? ASIndexPathsForTwoDimensionalArray(nodes) : nil; -} - -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind -{ - return _editingNodes[kind] ? : [NSMutableArray array]; -} - -- (NSMutableArray *)completedNodesOfKind:(NSString *)kind -{ - return _completedNodes[kind]; -} - -#pragma mark - Data Querying (External API) - -- (NSUInteger)numberOfSections -{ - ASDisplayNodeAssertMainThread(); - return [_nodeContexts[ASDataControllerRowNodeKind] count]; -} - -- (NSUInteger)numberOfRowsInSection:(NSUInteger)section -{ - ASDisplayNodeAssertMainThread(); - NSArray *contextSections = _nodeContexts[ASDataControllerRowNodeKind]; - return (section < contextSections.count) ? [contextSections[section] count] : 0; -} - -- (NSUInteger)completedNumberOfSections -{ - ASDisplayNodeAssertMainThread(); - return [[self completedNodes] count]; -} - -- (NSUInteger)completedNumberOfRowsInSection:(NSUInteger)section -{ - ASDisplayNodeAssertMainThread(); - NSArray *completedNodes = [self completedNodes]; - return (section < completedNodes.count) ? [completedNodes[section] count] : 0; -} - -- (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - - NSArray *contexts = _nodeContexts[ASDataControllerRowNodeKind]; - NSInteger section = indexPath.section; - NSInteger row = indexPath.row; - ASIndexedNodeContext *context = nil; - - if (section >= 0 && row >= 0 && section < contexts.count) { - NSArray *completedNodesSection = contexts[section]; - if (row < completedNodesSection.count) { - context = completedNodesSection[row]; - } - } - - return context.node; -} - -- (ASCellNode *)nodeAtCompletedIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - - NSArray *completedNodes = [self completedNodes]; - NSInteger section = indexPath.section; - NSInteger row = indexPath.row; - ASCellNode *node = nil; - - if (section >= 0 && row >= 0 && section < completedNodes.count) { - NSArray *completedNodesSection = completedNodes[section]; - if (row < completedNodesSection.count) { - node = completedNodesSection[row]; - } - } - - return node; -} - -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; -{ - ASDisplayNodeAssertMainThread(); - if (cellNode == nil) { - return nil; - } - - NSString *kind = cellNode.supplementaryElementKind ?: ASDataControllerRowNodeKind; - NSArray *contexts = _nodeContexts[kind]; - - // Check if the cached index path is still correct. - NSIndexPath *indexPath = cellNode.cachedIndexPath; - if (indexPath != nil) { - ASIndexedNodeContext *context = ASGetElementInTwoDimensionalArray(contexts, indexPath); - if (context.nodeIfAllocated == cellNode) { - return indexPath; - } else { - indexPath = nil; - } - } - - // Loop through each section to look for the node context - NSInteger section = 0; - for (NSArray *nodeContexts in contexts) { - NSUInteger item = [nodeContexts indexOfObjectPassingTest:^BOOL(ASIndexedNodeContext * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - return obj.nodeIfAllocated == cellNode; - }]; - if (item != NSNotFound) { - indexPath = [NSIndexPath indexPathForItem:item inSection:section]; - break; - } - section += 1; - } - cellNode.cachedIndexPath = indexPath; - return indexPath; -} - -- (NSIndexPath *)completedIndexPathForNode:(ASCellNode *)cellNode -{ - ASDisplayNodeAssertMainThread(); - if (cellNode == nil) { - return nil; - } - - NSInteger section = 0; - // Loop through each section to look for the cellNode - NSString *kind = cellNode.supplementaryElementKind ?: ASDataControllerRowNodeKind; - for (NSArray *sectionNodes in [self completedNodesOfKind:kind]) { - NSUInteger item = [sectionNodes indexOfObjectIdenticalTo:cellNode]; - if (item != NSNotFound) { - return [NSIndexPath indexPathForItem:item inSection:section]; - } - section += 1; - } - - return nil; -} - -/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. -- (NSArray *)completedNodes -{ - ASDisplayNodeAssertMainThread(); - return _externalCompletedNodes ? : _completedNodes[ASDataControllerRowNodeKind]; -} - -- (void)moveCompletedNodeAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath -{ - ASDisplayNodeAssertMainThread(); - ASMoveElementInTwoDimensionalArray(_externalCompletedNodes, indexPath, newIndexPath); - ASMoveElementInTwoDimensionalArray(_completedNodes[ASDataControllerRowNodeKind], indexPath, newIndexPath); -} - -@end - -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK - -static volatile int64_t _totalExpectedItems = 0; -static volatile int64_t _totalMeasuredNodes = 0; - -@implementation ASDataController (WorkMeasuring) - -+ (void)_didLayoutNode -{ - int64_t measured = OSAtomicIncrement64(&_totalMeasuredNodes); - int64_t expected = _totalExpectedItems; - if (measured % 20 == 0 || measured == expected) { - NSLog(@"Data controller avoided work (underestimated): %lld / %lld", measured, expected); - } -} - -+ (void)_expectToInsertNodes:(NSUInteger)count -{ - OSAtomicAdd64((int64_t)count, &_totalExpectedItems); -} - -@end -#endif diff --git a/AsyncDisplayKit/Details/ASEnvironment.h b/AsyncDisplayKit/Details/ASEnvironment.h deleted file mode 100644 index e8f2bf6159..0000000000 --- a/AsyncDisplayKit/Details/ASEnvironment.h +++ /dev/null @@ -1,165 +0,0 @@ -// -// ASEnvironment.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import - -#import -#import - -@protocol ASEnvironment; -@class UITraitCollection; - -ASDISPLAYNODE_EXTERN_C_BEGIN -NS_ASSUME_NONNULL_BEGIN - -static const int kMaxEnvironmentStateBoolExtensions = 1; -static const int kMaxEnvironmentStateIntegerExtensions = 4; -static const int kMaxEnvironmentStateEdgeInsetExtensions = 1; - -#pragma mark - - -typedef struct ASEnvironmentStateExtensions { - // Values to store extensions - BOOL boolExtensions[kMaxEnvironmentStateBoolExtensions]; - NSInteger integerExtensions[kMaxEnvironmentStateIntegerExtensions]; - UIEdgeInsets edgeInsetsExtensions[kMaxEnvironmentStateEdgeInsetExtensions]; -} ASEnvironmentStateExtensions; - -#pragma mark - ASEnvironmentLayoutOptionsState - -typedef struct ASEnvironmentLayoutOptionsState { - CGFloat spacingBefore;// = 0; - CGFloat spacingAfter;// = 0; - CGFloat flexGrow;// = 0; - CGFloat flexShrink;// = 0; - ASDimension flexBasis;// = ASDimensionAuto; - ASStackLayoutAlignSelf alignSelf;// = ASStackLayoutAlignSelfAuto; - CGFloat ascender;// = 0; - CGFloat descender;// = 0; - - CGPoint layoutPosition;// = CGPointZero; - - struct ASEnvironmentStateExtensions _extensions; -} ASEnvironmentLayoutOptionsState; -/// Should be used to create an ASEnvironmentLayoutOptionsState -extern ASEnvironmentLayoutOptionsState ASEnvironmentLayoutOptionsStateMakeDefault(); - - -#pragma mark - ASEnvironmentHierarchyState - -typedef struct ASEnvironmentHierarchyState { - unsigned rasterized:1; // = NO - unsigned rangeManaged:1; // = NO - unsigned transitioningSupernodes:1; // = NO - unsigned layoutPending:1; // = NO -} ASEnvironmentHierarchyState; -extern ASEnvironmentHierarchyState ASEnvironmentHierarchyStateMakeDefault(); - -#pragma mark - ASEnvironmentDisplayTraits - -typedef struct ASEnvironmentTraitCollection { - CGFloat displayScale; - UIUserInterfaceSizeClass horizontalSizeClass; - UIUserInterfaceIdiom userInterfaceIdiom; - UIUserInterfaceSizeClass verticalSizeClass; - UIForceTouchCapability forceTouchCapability; - - CGSize containerSize; -} ASEnvironmentTraitCollection; -extern ASEnvironmentTraitCollection ASEnvironmentTraitCollectionMakeDefault(); - -extern ASEnvironmentTraitCollection ASEnvironmentTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection); -extern BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnvironmentTraitCollection lhs, ASEnvironmentTraitCollection rhs); -extern NSString *NSStringFromASEnvironmentTraitCollection(ASEnvironmentTraitCollection traits); -#pragma mark - ASEnvironmentState - -typedef struct ASEnvironmentState { - struct ASEnvironmentHierarchyState hierarchyState; - struct ASEnvironmentLayoutOptionsState layoutOptionsState; - struct ASEnvironmentTraitCollection environmentTraitCollection; -} ASEnvironmentState; -extern ASEnvironmentState ASEnvironmentStateMakeDefault(); - -ASDISPLAYNODE_EXTERN_C_END - -@class ASTraitCollection; - -#pragma mark - ASEnvironment - -/** - * ASEnvironment allows objects that conform to the ASEnvironment protocol to be able to propagate specific States - * defined in an ASEnvironmentState up and down the ASEnvironment tree. To be able to define how merges of - * States should happen, specific merge functions can be provided - */ -@protocol ASEnvironment - -/// The environment collection of an object which class conforms to the ASEnvironment protocol -- (ASEnvironmentState)environmentState; -- (void)setEnvironmentState:(ASEnvironmentState)environmentState; - -/// Returns the parent of an object which class conforms to the ASEnvironment protocol -- (id _Nullable)parent; - -/// Returns all children of an object which class conforms to the ASEnvironment protocol -- (nullable NSArray> *)children; - -/// Classes should implement this method and return YES / NO dependent if upward propagation is enabled or not -// Currently this is disabled as propagation of any attributions besides trait collections is not supported at the moment -// - (BOOL)supportsUpwardPropagation; - -/// Classes should implement this method and return YES / NO dependent if downware propagation is enabled or not -- (BOOL)supportsTraitsCollectionPropagation; - -/// Returns an NSObject-representation of the environment's ASEnvironmentDisplayTraits -- (ASTraitCollection *)asyncTraitCollection; - -/// Returns a struct-representation of the environment's ASEnvironmentDisplayTraits. This only exists as a internal -/// convenience method. Users should access the trait collections through the NSObject based asyncTraitCollection API -- (ASEnvironmentTraitCollection)environmentTraitCollection; - -/// sets a trait collection on this environment state. -- (void)setEnvironmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection; -@end - -// ASCollection/TableNodes don't actually have ASCellNodes as subnodes. Because of this we can't rely on display trait -// downward propagation via ASEnvironment. Instead if the new environmentState has displayTraits that are different from -// the cells', then we propagate downward explicitly and request a relayout. -// -// If there is any new downward propagating state, it should be added to this define. -// -// If the only change in a trait collection is that its dislplayContext has gone from non-nil to nil, -// assume that we are clearing the context as part of a ASVC dealloc and do not trigger a layout. -// -// This logic is used in both ASCollectionNode and ASTableNode -#define ASEnvironmentCollectionTableSetEnvironmentState(lock) \ -- (void)setEnvironmentState:(ASEnvironmentState)environmentState\ -{\ - ASDN::MutexLocker l(lock);\ - ASEnvironmentTraitCollection oldTraits = self.environmentState.environmentTraitCollection;\ - [super setEnvironmentState:environmentState];\ -\ - /* Extra Trait Collection Handling */\ - /* If the node is not loaded yet don't do anything as otherwise the access of the view will trigger a load*/\ - if (!self.isNodeLoaded) { return; } \ - ASEnvironmentTraitCollection currentTraits = environmentState.environmentTraitCollection;\ - if (ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(currentTraits, oldTraits) == NO) {\ - /* Must dispatch to main for self.view && [self.view.dataController completedNodes]*/ \ - ASPerformBlockOnMainThread(^{\ - NSArray *> *completedNodes = [self.view.dataController completedNodes];\ - for (NSArray *sectionArray in completedNodes) {\ - for (ASCellNode *cellNode in sectionArray) {\ - ASEnvironmentStatePropagateDown(cellNode, currentTraits);\ - }\ - }\ - });\ - }\ -}\ - -NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASEnvironment.mm b/AsyncDisplayKit/Details/ASEnvironment.mm deleted file mode 100644 index 0754759670..0000000000 --- a/AsyncDisplayKit/Details/ASEnvironment.mm +++ /dev/null @@ -1,123 +0,0 @@ -// -// ASEnvironment.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import "ASEnvironmentInternal.h" -#import "ASAvailability.h" -#import "ASObjectDescriptionHelpers.h" - -ASEnvironmentLayoutOptionsState ASEnvironmentLayoutOptionsStateMakeDefault() -{ - return (ASEnvironmentLayoutOptionsState) { - // Default values can be defined in here - }; -} - -ASEnvironmentHierarchyState ASEnvironmentHierarchyStateMakeDefault() -{ - return (ASEnvironmentHierarchyState) { - // Default values can be defined in here - }; -} - -ASEnvironmentTraitCollection ASEnvironmentTraitCollectionMakeDefault() -{ - return (ASEnvironmentTraitCollection) { - // Default values can be defined in here - .userInterfaceIdiom = UIUserInterfaceIdiomUnspecified, - .containerSize = CGSizeZero, - }; -} - -ASEnvironmentTraitCollection ASEnvironmentTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) -{ - ASEnvironmentTraitCollection environmentTraitCollection = ASEnvironmentTraitCollectionMakeDefault(); - if (AS_AT_LEAST_IOS8) { - environmentTraitCollection.displayScale = traitCollection.displayScale; - environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; - environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; - environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; - if (AS_AT_LEAST_IOS9) { - environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; - } - } - return environmentTraitCollection; -} - -BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnvironmentTraitCollection lhs, ASEnvironmentTraitCollection rhs) -{ - return - lhs.verticalSizeClass == rhs.verticalSizeClass && - lhs.horizontalSizeClass == rhs.horizontalSizeClass && - lhs.displayScale == rhs.displayScale && - lhs.userInterfaceIdiom == rhs.userInterfaceIdiom && - lhs.forceTouchCapability == rhs.forceTouchCapability && - CGSizeEqualToSize(lhs.containerSize, rhs.containerSize); -} - -// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline -ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceIdiom(UIUserInterfaceIdiom idiom) { - switch (idiom) { - case UIUserInterfaceIdiomTV: - return @"TV"; - case UIUserInterfaceIdiomPad: - return @"Pad"; - case UIUserInterfaceIdiomPhone: - return @"Phone"; - case UIUserInterfaceIdiomCarPlay: - return @"CarPlay"; - default: - return @"Unspecified"; - } -} - -// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline -ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIForceTouchCapability(UIForceTouchCapability capability) { - switch (capability) { - case UIForceTouchCapabilityAvailable: - return @"Available"; - case UIForceTouchCapabilityUnavailable: - return @"Unavailable"; - default: - return @"Unknown"; - } -} - -// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline -ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceSizeClass(UIUserInterfaceSizeClass sizeClass) { - switch (sizeClass) { - case UIUserInterfaceSizeClassCompact: - return @"Compact"; - case UIUserInterfaceSizeClassRegular: - return @"Regular"; - default: - return @"Unspecified"; - } -} - -NSString *NSStringFromASEnvironmentTraitCollection(ASEnvironmentTraitCollection traits) -{ - NSMutableArray *props = [NSMutableArray array]; - [props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }]; - [props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }]; - [props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }]; - [props addObject:@{ @"verticalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.verticalSizeClass) }]; - [props addObject:@{ @"forceTouchCapability": AS_NSStringFromUIForceTouchCapability(traits.forceTouchCapability) }]; - return ASObjectDescriptionMakeWithoutObject(props); -} - -ASEnvironmentState ASEnvironmentStateMakeDefault() -{ - return (ASEnvironmentState) { - .layoutOptionsState = ASEnvironmentLayoutOptionsStateMakeDefault(), - .hierarchyState = ASEnvironmentHierarchyStateMakeDefault(), - .environmentTraitCollection = ASEnvironmentTraitCollectionMakeDefault() - }; -} - diff --git a/AsyncDisplayKit/Details/ASFlowLayoutController.h b/AsyncDisplayKit/Details/ASFlowLayoutController.h deleted file mode 100644 index de87c11f4e..0000000000 --- a/AsyncDisplayKit/Details/ASFlowLayoutController.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// ASFlowLayoutController.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCellNode; - -typedef NS_ENUM(NSUInteger, ASFlowLayoutDirection) { - ASFlowLayoutDirectionVertical, - ASFlowLayoutDirectionHorizontal, -}; - -@protocol ASFlowLayoutControllerDataSource - -- (NSArray *> *)completedNodes; // This provides access to ASDataController's _completedNodes multidimensional array. - -@end - -/** - * An optimized flow layout controller that supports only vertical or horizontal scrolling, not simultaneously two-dimensional scrolling. - * It is used for all ASTableViews, and may be used with ASCollectionView. - */ -@interface ASFlowLayoutController : ASAbstractLayoutController - -@property (nonatomic, readonly, assign) ASFlowLayoutDirection layoutDirection; -@property (nonatomic, readwrite, weak) id dataSource; - -- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/AsyncDisplayKit/Details/ASFlowLayoutController.mm deleted file mode 100644 index 15dcbfcc26..0000000000 --- a/AsyncDisplayKit/Details/ASFlowLayoutController.mm +++ /dev/null @@ -1,209 +0,0 @@ -// -// ASFlowLayoutController.mm -// AsyncDisplayKit -// -// 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 "ASFlowLayoutController.h" -#import "ASAssert.h" -#import "ASDisplayNode.h" -#import "ASIndexPath.h" -#import "CoreGraphics+ASConvenience.h" - -#include -#include - -@interface ASFlowLayoutController() -{ - ASIndexPathRange _visibleRange; - std::vector _rangesByType; // All ASLayoutRangeTypes besides visible. -} - -@end - -@implementation ASFlowLayoutController - -- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection -{ - if (!(self = [super init])) { - return nil; - } - _layoutDirection = layoutDirection; - _rangesByType = std::vector(ASLayoutRangeTypeCount); - return self; -} - -#pragma mark - Visible Indices - -- (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths -{ - _visibleRange = [self indexPathRangeForIndexPaths:indexPaths]; -} - -/** - * IndexPath array for the element in the working range. - */ - -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - CGSize viewportSize = [self viewportSize]; - - CGFloat viewportDirectionalSize = 0.0; - ASDirectionalScreenfulBuffer directionalBuffer = { 0, 0 }; - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - - if (_layoutDirection == ASFlowLayoutDirectionHorizontal) { - ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || - scrollDirection == ASScrollDirectionLeft || - scrollDirection == ASScrollDirectionRight, @"Invalid scroll direction"); - - viewportDirectionalSize = viewportSize.width; - directionalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters); - } else { - ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || - scrollDirection == ASScrollDirectionUp || - scrollDirection == ASScrollDirectionDown, @"Invalid scroll direction"); - - viewportDirectionalSize = viewportSize.height; - directionalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters); - } - - ASIndexPath startPath = [self findIndexPathAtDistance:(-directionalBuffer.negativeDirection * viewportDirectionalSize) - fromIndexPath:_visibleRange.start]; - - ASIndexPath endPath = [self findIndexPathAtDistance:(directionalBuffer.positiveDirection * viewportDirectionalSize) - fromIndexPath:_visibleRange.end]; - - ASDisplayNodeAssert(startPath.section <= endPath.section, @"startPath should never begin at a further position than endPath"); - - NSMutableSet *indexPathSet = [[NSMutableSet alloc] init]; - - NSArray *completedNodes = [_dataSource completedNodes]; - - ASIndexPath currPath = startPath; - - while (!ASIndexPathEqualToIndexPath(currPath, endPath)) { - [indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:currPath]]; - currPath.row++; - - // Once we reach the end of the section, advance to the next one. Keep advancing if the next section is zero-sized. - while (currPath.row >= [(NSArray *)completedNodes[currPath.section] count] && currPath.section < endPath.section) { - currPath.row = 0; - currPath.section++; - } - } - ASDisplayNodeAssert(currPath.section <= endPath.section, @"currPath should never reach a further section than endPath"); - - [indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:endPath]]; - - return indexPathSet; -} - -#pragma mark - Utility - -- (ASIndexPathRange)indexPathRangeForIndexPaths:(NSArray *)indexPaths -{ - // Set up an initial value so the MIN and MAX can work in the enumeration. - __block ASIndexPath currentIndexPath = [[indexPaths firstObject] ASIndexPathValue]; - __block ASIndexPathRange range; - range.start = currentIndexPath; - range.end = currentIndexPath; - - for (NSIndexPath *indexPath in indexPaths) { - currentIndexPath = [indexPath ASIndexPathValue]; - range.start = ASIndexPathMinimum(range.start, currentIndexPath); - range.end = ASIndexPathMaximum(range.end, currentIndexPath); - } - return range; -} - -- (ASIndexPath)findIndexPathAtDistance:(CGFloat)distance fromIndexPath:(ASIndexPath)start -{ - // "end" is the index path we'll advance until we have gone far enough from "start" to reach "distance" - ASIndexPath end = start; - // "previous" will store one iteration before "end", in case we go too far and need to reset "end" to be "previous" - ASIndexPath previous = start; - - NSArray *completedNodes = [_dataSource completedNodes]; - NSUInteger numberOfSections = [completedNodes count]; - NSUInteger numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count]; - - // If "distance" is negative, advance "end" backwards across rows and sections. - // Otherwise, advance forward. In either case, bring "distance" closer to zero by the dimension of each row passed. - if (distance < 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) { - while (distance < 0.0 && end.section >= 0 && end.row >= 0) { - previous = end; - ASDisplayNode *node = completedNodes[end.section][end.row]; - CGSize size = node.calculatedSize; - distance += (_layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height); - end.row--; - // If we've gone to a negative row, set to the last row of the previous section. While loop is required to handle empty sections. - while (end.row < 0 && end.section > 0) { - end.section--; - numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count]; - end.row = numberOfRowsInSection - 1; - } - } - - if (end.row < 0) { - end = previous; - } - } else { - while (distance > 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) { - previous = end; - ASDisplayNode *node = completedNodes[end.section][end.row]; - CGSize size = node.calculatedSize; - distance -= _layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height; - - end.row++; - // If we've gone beyond the section, reset to the beginning of the next section. While loop is required to handle empty sections. - while (end.row >= numberOfRowsInSection && end.section < numberOfSections - 1) { - end.row = 0; - end.section++; - numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count]; - } - } - - if (end.row >= numberOfRowsInSection) { - end = previous; - } - } - - return end; -} - -- (NSInteger)flowLayoutDistanceForRange:(ASIndexPathRange)range -{ - // This method should only be called with the range in proper order (start comes before end). - ASDisplayNodeAssert(ASIndexPathEqualToIndexPath(ASIndexPathMinimum(range.start, range.end), range.start), @"flowLayoutDistanceForRange: called with invalid range"); - - if (ASIndexPathEqualToIndexPath(range.start, range.end)) { - return 0; - } - - NSInteger totalRowCount = 0; - NSUInteger numberOfRowsInSection = 0; - NSArray *completedNodes = [_dataSource completedNodes]; - - for (NSInteger section = range.start.section; section <= range.end.section; section++) { - numberOfRowsInSection = [(NSArray *)completedNodes[section] count]; - totalRowCount += numberOfRowsInSection; - - if (section == range.start.section) { - // For the start section, make sure we don't count the rows before the start row. - totalRowCount -= range.start.row; - } else if (section == range.end.section) { - // For the start section, make sure we don't count the rows after the end row. - totalRowCount -= (numberOfRowsInSection - (range.end.row + 1)); - } - } - - ASDisplayNodeAssert(totalRowCount >= 0, @"totalRowCount in flowLayoutDistanceForRange: should not be negative"); - return totalRowCount; -} - -@end diff --git a/AsyncDisplayKit/Details/ASIndexPath.h b/AsyncDisplayKit/Details/ASIndexPath.h deleted file mode 100644 index 32ae9f9add..0000000000 --- a/AsyncDisplayKit/Details/ASIndexPath.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// ASIndexPath.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import -#import - -typedef struct { - NSInteger section; - NSInteger row; -} ASIndexPath; - -typedef struct { - ASIndexPath start; - ASIndexPath end; -} ASIndexPathRange; - -NS_ASSUME_NONNULL_BEGIN - -ASDISPLAYNODE_EXTERN_C_BEGIN - - -extern ASIndexPath ASIndexPathMake(NSInteger section, NSInteger row); - -extern BOOL ASIndexPathEqualToIndexPath(ASIndexPath first, ASIndexPath second); - -extern ASIndexPath ASIndexPathMinimum(ASIndexPath first, ASIndexPath second); - -extern ASIndexPath ASIndexPathMaximum(ASIndexPath first, ASIndexPath second); - -extern ASIndexPathRange ASIndexPathRangeMake(ASIndexPath first, ASIndexPath second); - -extern BOOL ASIndexPathRangeEqualToIndexPathRange(ASIndexPathRange first, ASIndexPathRange second); - -ASDISPLAYNODE_EXTERN_C_END - -@interface NSIndexPath (ASIndexPathAdditions) - -+ (NSIndexPath *)indexPathWithASIndexPath:(ASIndexPath)indexPath; -- (ASIndexPath)ASIndexPathValue; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASIndexPath.m b/AsyncDisplayKit/Details/ASIndexPath.m deleted file mode 100644 index bca083d5fc..0000000000 --- a/AsyncDisplayKit/Details/ASIndexPath.m +++ /dev/null @@ -1,75 +0,0 @@ -// -// ASIndexPath.m -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import "ASIndexPath.h" - -#import - -ASIndexPath ASIndexPathMake(NSInteger section, NSInteger row) -{ - ASIndexPath indexPath; - indexPath.section = section; - indexPath.row = row; - return indexPath; -} - -BOOL ASIndexPathEqualToIndexPath(ASIndexPath first, ASIndexPath second) -{ - return (first.section == second.section && first.row == second.row); -} - -ASIndexPath ASIndexPathMinimum(ASIndexPath first, ASIndexPath second) -{ - if (first.section < second.section) { - return first; - } else if (first.section > second.section) { - return second; - } else { - return (first.row < second.row ? first : second); - } -} - -ASIndexPath ASIndexPathMaximum(ASIndexPath first, ASIndexPath second) -{ - if (first.section > second.section) { - return first; - } else if (first.section < second.section) { - return second; - } else { - return (first.row > second.row ? first : second); - } -} - -ASIndexPathRange ASIndexPathRangeMake(ASIndexPath first, ASIndexPath second) -{ - ASIndexPathRange range; - range.start = ASIndexPathMinimum(first, second); - range.end = ASIndexPathMaximum(first, second); - return range; -} - -BOOL ASIndexPathRangeEqualToIndexPathRange(ASIndexPathRange first, ASIndexPathRange second) -{ - return ASIndexPathEqualToIndexPath(first.start, second.start) && ASIndexPathEqualToIndexPath(first.end, second.end); -} - -@implementation NSIndexPath (ASIndexPathAdditions) - -+ (NSIndexPath *)indexPathWithASIndexPath:(ASIndexPath)indexPath -{ - return [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section]; -} - -- (ASIndexPath)ASIndexPathValue -{ - return ASIndexPathMake(self.section, self.row); -} - -@end diff --git a/AsyncDisplayKit/Details/ASTraitCollection.h b/AsyncDisplayKit/Details/ASTraitCollection.h deleted file mode 100644 index 19e23131cc..0000000000 --- a/AsyncDisplayKit/Details/ASTraitCollection.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// ASTraitCollection.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import -#import - -@interface ASTraitCollection : NSObject - -@property (nonatomic, assign, readonly) CGFloat displayScale; -@property (nonatomic, assign, readonly) UIUserInterfaceSizeClass horizontalSizeClass; -@property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom; -@property (nonatomic, assign, readonly) UIUserInterfaceSizeClass verticalSizeClass; -@property (nonatomic, assign, readonly) UIForceTouchCapability forceTouchCapability; -@property (nonatomic, assign, readonly) CGSize containerSize; - - -+ (ASTraitCollection *)traitCollectionWithASEnvironmentTraitCollection:(ASEnvironmentTraitCollection)traits; - -+ (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection - containerSize:(CGSize)windowSize; - - -+ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize; - - -- (ASEnvironmentTraitCollection)environmentTraitCollection; -- (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection; - -@end diff --git a/AsyncDisplayKit/Details/ASTraitCollection.m b/AsyncDisplayKit/Details/ASTraitCollection.m deleted file mode 100644 index c3b83dd8ee..0000000000 --- a/AsyncDisplayKit/Details/ASTraitCollection.m +++ /dev/null @@ -1,111 +0,0 @@ -// -// ASTraitCollection.m -// AsyncDisplayKit -// -// Created by Ricky Cancro on 5/4/16. -// -// 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 "ASTraitCollection.h" -#import - -@implementation ASTraitCollection - -- (instancetype)initWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize -{ - self = [super init]; - if (self) { - _displayScale = displayScale; - _userInterfaceIdiom = userInterfaceIdiom; - _horizontalSizeClass = horizontalSizeClass; - _verticalSizeClass = verticalSizeClass; - _forceTouchCapability = forceTouchCapability; - _containerSize = windowSize; - } - return self; -} - -+ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize -{ - return [[[self class] alloc] initWithDisplayScale:displayScale - userInterfaceIdiom:userInterfaceIdiom - horizontalSizeClass:horizontalSizeClass - verticalSizeClass:verticalSizeClass - forceTouchCapability:forceTouchCapability - containerSize:windowSize]; -} - -+ (ASTraitCollection *)traitCollectionWithASEnvironmentTraitCollection:(ASEnvironmentTraitCollection)traits -{ - return [[[self class] alloc] initWithDisplayScale:traits.displayScale - userInterfaceIdiom:traits.userInterfaceIdiom - horizontalSizeClass:traits.horizontalSizeClass - verticalSizeClass:traits.verticalSizeClass - forceTouchCapability:traits.forceTouchCapability - containerSize:traits.containerSize]; - -} - -+ (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection - containerSize:(CGSize)windowSize -{ - ASTraitCollection *asyncTraitCollection = nil; - if (AS_AT_LEAST_IOS9) { - asyncTraitCollection = [[[self class] alloc] initWithDisplayScale:traitCollection.displayScale - userInterfaceIdiom:traitCollection.userInterfaceIdiom - horizontalSizeClass:traitCollection.horizontalSizeClass - verticalSizeClass:traitCollection.verticalSizeClass - forceTouchCapability:traitCollection.forceTouchCapability - containerSize:windowSize]; - } - else if (AS_AT_LEAST_IOS8) { - asyncTraitCollection = [[[self class] alloc] initWithDisplayScale:traitCollection.displayScale - userInterfaceIdiom:traitCollection.userInterfaceIdiom - horizontalSizeClass:traitCollection.horizontalSizeClass - verticalSizeClass:traitCollection.verticalSizeClass - forceTouchCapability:0 - containerSize:windowSize]; - } else { - asyncTraitCollection = [[[self class] alloc] init]; - } - - return asyncTraitCollection; -} - -- (ASEnvironmentTraitCollection)environmentTraitCollection -{ - return (ASEnvironmentTraitCollection) { - .displayScale = self.displayScale, - .horizontalSizeClass = self.horizontalSizeClass, - .userInterfaceIdiom = self.userInterfaceIdiom, - .verticalSizeClass = self.verticalSizeClass, - .forceTouchCapability = self.forceTouchCapability, - .containerSize = self.containerSize, - }; -} - -- (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection -{ - return self.displayScale == traitCollection.displayScale && - self.horizontalSizeClass == traitCollection.horizontalSizeClass && - self.verticalSizeClass == traitCollection.verticalSizeClass && - self.userInterfaceIdiom == traitCollection.userInterfaceIdiom && - CGSizeEqualToSize(self.containerSize, traitCollection.containerSize) && - self.forceTouchCapability == traitCollection.forceTouchCapability; -} - -@end diff --git a/AsyncDisplayKit/Details/CoreGraphics+ASConvenience.m b/AsyncDisplayKit/Details/CoreGraphics+ASConvenience.m deleted file mode 100644 index 98a5c90548..0000000000 --- a/AsyncDisplayKit/Details/CoreGraphics+ASConvenience.m +++ /dev/null @@ -1,74 +0,0 @@ -// -// CGRect+ASConvenience.m -// AsyncDisplayKit -// -// 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 "CoreGraphics+ASConvenience.h" - -ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection, - ASRangeTuningParameters rangeTuningParameters) -{ - ASDirectionalScreenfulBuffer horizontalBuffer = {0, 0}; - BOOL movingRight = ASScrollDirectionContainsRight(scrollDirection); - - horizontalBuffer.positiveDirection = movingRight ? rangeTuningParameters.leadingBufferScreenfuls - : rangeTuningParameters.trailingBufferScreenfuls; - horizontalBuffer.negativeDirection = movingRight ? rangeTuningParameters.trailingBufferScreenfuls - : rangeTuningParameters.leadingBufferScreenfuls; - return horizontalBuffer; -} - -ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection, - ASRangeTuningParameters rangeTuningParameters) -{ - ASDirectionalScreenfulBuffer verticalBuffer = {0, 0}; - BOOL movingDown = ASScrollDirectionContainsDown(scrollDirection); - - verticalBuffer.positiveDirection = movingDown ? rangeTuningParameters.leadingBufferScreenfuls - : rangeTuningParameters.trailingBufferScreenfuls; - verticalBuffer.negativeDirection = movingDown ? rangeTuningParameters.trailingBufferScreenfuls - : rangeTuningParameters.leadingBufferScreenfuls; - return verticalBuffer; -} - -CGRect CGRectExpandHorizontally(CGRect rect, ASDirectionalScreenfulBuffer buffer) -{ - CGFloat negativeDirectionWidth = buffer.negativeDirection * rect.size.width; - CGFloat positiveDirectionWidth = buffer.positiveDirection * rect.size.width; - rect.size.width = negativeDirectionWidth + rect.size.width + positiveDirectionWidth; - rect.origin.x -= negativeDirectionWidth; - return rect; -} - -CGRect CGRectExpandVertically(CGRect rect, ASDirectionalScreenfulBuffer buffer) -{ - CGFloat negativeDirectionHeight = buffer.negativeDirection * rect.size.height; - CGFloat positiveDirectionHeight = buffer.positiveDirection * rect.size.height; - rect.size.height = negativeDirectionHeight + rect.size.height + positiveDirectionHeight; - rect.origin.y -= negativeDirectionHeight; - return rect; -} - -CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect, ASRangeTuningParameters tuningParameters, - ASScrollDirection scrollableDirections, ASScrollDirection scrollDirection) -{ - // Can scroll horizontally - expand the range appropriately - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { - ASDirectionalScreenfulBuffer horizontalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters); - rect = CGRectExpandHorizontally(rect, horizontalBuffer); - } - - // Can scroll vertically - expand the range appropriately - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { - ASDirectionalScreenfulBuffer verticalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters); - rect = CGRectExpandVertically(rect, verticalBuffer); - } - - return rect; -} - diff --git a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m deleted file mode 100644 index 9bdb7b6c09..0000000000 --- a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m +++ /dev/null @@ -1,20 +0,0 @@ -// -// UICollectionViewLayout+ASConvenience.m -// AsyncDisplayKit -// -// 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 "UICollectionViewLayout+ASConvenience.h" - -@implementation UICollectionViewLayout (ASConvenience) - -- (BOOL)asdk_isFlowLayout -{ - return [self isKindOfClass:[UICollectionViewFlowLayout class]]; -} - -@end diff --git a/AsyncDisplayKit/Layout/ASDimension.mm b/AsyncDisplayKit/Layout/ASDimension.mm deleted file mode 100644 index cb8c358d9f..0000000000 --- a/AsyncDisplayKit/Layout/ASDimension.mm +++ /dev/null @@ -1,267 +0,0 @@ -// -// ASDimension.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import "ASDimension.h" -#import "ASAssert.h" -#import "CoreGraphics+ASConvenience.h" - -#pragma mark - ASDimension - -ASDimension const ASDimensionAuto = {ASDimensionUnitAuto, 0}; - -ASOVERLOADABLE ASDimension ASDimensionMake(NSString *dimension) -{ - if (dimension.length > 0) { - - // Handle points - if ([dimension hasSuffix:@"pt"]) { - return ASDimensionMake(ASDimensionUnitPoints, ASCGFloatFromString(dimension)); - } - - // Handle auto - if ([dimension isEqualToString:@"auto"]) { - return ASDimensionAuto; - } - - // Handle percent - if ([dimension hasSuffix:@"%"]) { - return ASDimensionMake(ASDimensionUnitFraction, (ASCGFloatFromString(dimension) / 100.0)); - } - } - - ASDisplayNodeCAssert(NO, @"Parsing dimension failed for: %@", dimension); - return ASDimensionAuto; -} - -NSString *NSStringFromASDimension(ASDimension dimension) -{ - switch (dimension.unit) { - case ASDimensionUnitPoints: - return [NSString stringWithFormat:@"%.0fpt", dimension.value]; - case ASDimensionUnitFraction: - return [NSString stringWithFormat:@"%.0f%%", dimension.value * 100.0]; - case ASDimensionUnitAuto: - return @"Auto"; - } -} - - -#pragma mark - NSNumber+ASDimension - -@implementation NSNumber (ASDimension) - -- (ASDimension)as_pointDimension -{ - return ASDimensionMake(ASDimensionUnitPoints, ASCGFloatFromNumber(self)); -} - -- (ASDimension)as_fractionDimension -{ - return ASDimensionMake(ASDimensionUnitFraction, ASCGFloatFromNumber(self)); -} - -@end - - -#pragma mark - ASLayoutSize - -ASLayoutSize const ASLayoutSizeAuto = {ASDimensionAuto, ASDimensionAuto}; - -// ** Resolve this relative size relative to a parent size. */ -ASDISPLAYNODE_INLINE CGSize ASLayoutSizeResolveSize(ASLayoutSize layoutSize, CGSize parentSize, CGSize autoSize) -{ - return CGSizeMake(ASDimensionResolve(layoutSize.width, parentSize.width, autoSize.width), - ASDimensionResolve(layoutSize.height, parentSize.height, autoSize.height)); -} - - -#pragma mark - ASLayoutElementSize - -NSString *NSStringFromASLayoutElementSize(ASLayoutElementSize size) -{ - return [NSString stringWithFormat: - @"", - NSStringFromASLayoutSize(ASLayoutSizeMake(size.width, size.height)), - NSStringFromASLayoutSize(ASLayoutSizeMake(size.minWidth, size.minHeight)), - NSStringFromASLayoutSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight))]; -} - -ASDISPLAYNODE_INLINE void ASLayoutElementSizeConstrain(CGFloat minVal, CGFloat exactVal, CGFloat maxVal, CGFloat *outMin, CGFloat *outMax) -{ - NSCAssert(!isnan(minVal), @"minVal must not be NaN"); - NSCAssert(!isnan(maxVal), @"maxVal must not be NaN"); - // Avoid use of min/max primitives since they're harder to reason - // about in the presence of NaN (in exactVal) - // Follow CSS: min overrides max overrides exact. - - // Begin with the min/max range - *outMin = minVal; - *outMax = maxVal; - if (maxVal <= minVal) { - // min overrides max and exactVal is irrelevant - *outMax = minVal; - return; - } - if (isnan(exactVal)) { - // no exact value, so leave as a min/max range - return; - } - if (exactVal > maxVal) { - // clip to max value - *outMin = maxVal; - } else if (exactVal < minVal) { - // clip to min value - *outMax = minVal; - } else { - // use exact value - *outMin = *outMax = exactVal; - } -} - -ASSizeRange ASLayoutElementSizeResolveAutoSize(ASLayoutElementSize size, const CGSize parentSize, ASSizeRange autoASSizeRange) -{ - CGSize resolvedExact = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.width, size.height), parentSize, {NAN, NAN}); - CGSize resolvedMin = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.minWidth, size.minHeight), parentSize, autoASSizeRange.min); - CGSize resolvedMax = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight), parentSize, autoASSizeRange.max); - - CGSize rangeMin, rangeMax; - ASLayoutElementSizeConstrain(resolvedMin.width, resolvedExact.width, resolvedMax.width, &rangeMin.width, &rangeMax.width); - ASLayoutElementSizeConstrain(resolvedMin.height, resolvedExact.height, resolvedMax.height, &rangeMin.height, &rangeMax.height); - return {rangeMin, rangeMax}; -} - - -#pragma mark - ASSizeRange - -struct _Range { - CGFloat min; - CGFloat max; - - /** - Intersects another dimension range. If the other range does not overlap, this size range "wins" by returning a - single point within its own range that is closest to the non-overlapping range. - */ - _Range intersect(const _Range &other) const - { - CGFloat newMin = MAX(min, other.min); - CGFloat newMax = MIN(max, other.max); - if (newMin <= newMax) { - return {newMin, newMax}; - } else { - // No intersection. If we're before the other range, return our max; otherwise our min. - if (min < other.min) { - return {max, max}; - } else { - return {min, min}; - } - } - } -}; - -ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRange) -{ - auto w = _Range({sizeRange.min.width, sizeRange.max.width}).intersect({otherSizeRange.min.width, otherSizeRange.max.width}); - auto h = _Range({sizeRange.min.height, sizeRange.max.height}).intersect({otherSizeRange.min.height, otherSizeRange.max.height}); - return {{w.min, h.min}, {w.max, h.max}}; -} - -NSString *NSStringFromASSizeRange(ASSizeRange sizeRange) -{ - return [NSString stringWithFormat:@"", - NSStringFromCGSize(sizeRange.min), - NSStringFromCGSize(sizeRange.max)]; -} - - -#pragma mark - Deprecated - -ASDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) -{ - if (type == ASRelativeDimensionTypePoints) { - return ASDimensionMakeWithPoints(value); - } else if (type == ASRelativeDimensionTypeFraction) { - return ASDimensionMakeWithFraction(value); - } - - ASDisplayNodeCAssert(NO, @"ASRelativeDimensionMake does not support the given ASRelativeDimensionType"); - return ASDimensionMakeWithPoints(0); -} - -ASSizeRange ASSizeRangeMakeExactSize(CGSize size) -{ - return ASSizeRangeMake(size); -} - -ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained = {}; - -#pragma mark - ASRelativeSize - -ASLayoutSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) -{ - return ASLayoutSizeMake(width, height); -} - -ASLayoutSize ASRelativeSizeMakeWithCGSize(CGSize size) -{ - return ASRelativeSizeMake(ASRelativeDimensionMakeWithPoints(size.width), - ASRelativeDimensionMakeWithPoints(size.height)); -} - -ASLayoutSize ASRelativeSizeMakeWithFraction(CGFloat fraction) -{ - return ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(fraction), - ASRelativeDimensionMakeWithFraction(fraction)); -} - -BOOL ASRelativeSizeEqualToRelativeSize(ASLayoutSize lhs, ASLayoutSize rhs) -{ - return ASDimensionEqualToDimension(lhs.width, rhs.width) - && ASDimensionEqualToDimension(lhs.height, rhs.height); -} - - -#pragma mark - ASRelativeSizeRange - -ASRelativeSizeRange ASRelativeSizeRangeMake(ASLayoutSize min, ASLayoutSize max) -{ - ASRelativeSizeRange sizeRange; sizeRange.min = min; sizeRange.max = max; return sizeRange; -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASLayoutSize exact) -{ - return ASRelativeSizeRangeMake(exact, exact); -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) -{ - return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithCGSize(exact)); -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction) -{ - return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithFraction(fraction)); -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight) -{ - return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMake(exactWidth, exactHeight)); -} - -BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs) -{ - return ASRelativeSizeEqualToRelativeSize(lhs.min, rhs.min) && ASRelativeSizeEqualToRelativeSize(lhs.max, rhs.max); -} - -ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, - CGSize parentSize) -{ - return ASSizeRangeMake(ASLayoutSizeResolveSize(relativeSizeRange.min, parentSize, parentSize), - ASLayoutSizeResolveSize(relativeSizeRange.max, parentSize, parentSize)); -} diff --git a/AsyncDisplayKit/Layout/ASLayoutElementExtensibility.h b/AsyncDisplayKit/Layout/ASLayoutElementExtensibility.h deleted file mode 100644 index 7c9095e5f2..0000000000 --- a/AsyncDisplayKit/Layout/ASLayoutElementExtensibility.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// ASLayoutElementExtensibility.h -// AsyncDisplayKit -// -// Created by Michael Schneider on 3/29/16. -// -// 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 - -@protocol ASLayoutElementExtensibility - -// The maximum number of extended values per type are defined in ASEnvironment.h above the ASEnvironmentStateExtensions -// struct definition. If you try to set a value at an index after the maximum it will throw an assertion. - -- (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx; -- (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx; - -- (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx; -- (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx; - -- (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx; -- (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx; - -@end diff --git a/AsyncDisplayKit/Private/ASDataController+Subclasses.h b/AsyncDisplayKit/Private/ASDataController+Subclasses.h deleted file mode 100644 index 2012460f21..0000000000 --- a/AsyncDisplayKit/Private/ASDataController+Subclasses.h +++ /dev/null @@ -1,198 +0,0 @@ -// -// ASDataController+Subclasses.h -// AsyncDisplayKit -// -// 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. -// - -#pragma once -#import - -@class ASIndexedNodeContext; - -typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NSArray *indexPaths); - -@interface ASDataController (Subclasses) - -#pragma mark - Internal editing & completed store querying - -/** - * Provides a collection of index paths for nodes of the given kind that are currently in the editing store - */ -- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind; - -/** - * Read-only access to the underlying editing nodes of the given kind - */ -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind; - -/** - * Read only access to the underlying completed nodes of the given kind - */ -- (NSMutableArray *)completedNodesOfKind:(NSString *)kind; - -/** - * Ensure that next time `itemCountsFromDataSource` is called, new values are retrieved. - * - * This must be called on the main thread. - */ -- (void)invalidateDataSourceItemCounts; - -/** - * Returns the most recently gathered item counts from the data source. If the counts - * have been invalidated, this synchronously queries the data source and saves the result. - * - * This must be called on the main thread. - */ -- (std::vector)itemCountsFromDataSource; - -#pragma mark - Node sizing - -/** - * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. - * - * This method runs synchronously. - * @param batchCompletion A handler to be run after each batch is completed. It is executed synchronously on the calling thread. - */ -- (void)batchLayoutNodesFromContexts:(NSArray *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler; - -/** - * Provides the size range for a specific node during the layout process. - */ -- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -#pragma mark - Node & Section Insertion/Deletion API - -/** - * Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes. - */ -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock; - -/** - * Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes. - */ -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock; - -/** - * Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished. - */ -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock; - -/** - * Deletes the given sections of the specified kind in the backing store, calling completion on the main thread when finished. - */ -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock; - -#pragma mark - Data Manipulation Hooks - -/** - * Notifies the subclass to perform any work needed before the data controller is reloaded entirely - * - * @discussion This method will be performed before the data controller enters its editing queue. - * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or - * data stores before entering into editing the backing store on a background thread. - */ - - (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount; - -/** - * Notifies the subclass that the data controller is about to reload its data entirely - * - * @discussion This method will be performed on the data controller's editing background queue before the parent's - * concrete implementation. This is a great place to perform new node creation like supplementary views - * or header/footer nodes. - */ -- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount; - -/** - * Notifies the subclass to perform setup before sections are inserted in the data controller - * - * @discussion This method will be performed before the data controller enters its editing queue. - * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or - * data stores before entering into editing the backing store on a background thread. - * - * @param sections Indices of sections to be inserted - */ -- (void)prepareForInsertSections:(NSIndexSet *)sections; - -/** - * Notifies the subclass that the data controller will insert new sections at the given position - * - * @discussion This method will be performed on the data controller's editing background queue before the parent's - * concrete implementation. This is a great place to perform any additional transformations like supplementary views - * or header/footer nodes. - * - * @param sections Indices of sections to be inserted - */ -- (void)willInsertSections:(NSIndexSet *)sections; - -/** - * Notifies the subclass that the data controller will delete sections at the given positions - * - * @discussion This method will be performed on the data controller's editing background queue before the parent's - * concrete implementation. This is a great place to perform any additional transformations like supplementary views - * or header/footer nodes. - * - * @param sections Indices of sections to be deleted - */ -- (void)willDeleteSections:(NSIndexSet *)sections; - -/** - * Notifies the subclass that the data controller will move a section to a new position - * - * @discussion This method will be performed on the data controller's editing background queue before the parent's - * concrete implementation. This is a great place to perform any additional transformations like supplementary views - * or header/footer nodes. - * - * @param section Index of current section position - * @param newSection Index of new section position - */ -- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection; - -/** - * Notifies the subclass to perform setup before rows are inserted in the data controller. - * - * @discussion This method will be performed before the data controller enters its editing queue. - * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or - * data stores before entering into editing the backing store on a background thread. - * - * @param indexPaths Index paths for the rows to be inserted. - */ -- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Notifies the subclass that the data controller will insert new rows at the given index paths. - * - * @discussion This method will be performed on the data controller's editing background queue before the parent's - * concrete implementation. This is a great place to perform any additional transformations like supplementary views - * or header/footer nodes. - * - * @param indexPaths Index paths for the rows to be inserted. - */ -- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Notifies the subclass to perform setup before rows are deleted in the data controller. - * - * @discussion This method will be performed before the data controller enters its editing queue. - * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or - * data stores before entering into editing the backing store on a background thread. - * - * @param indexPaths Index paths for the rows to be deleted. - */ -- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Notifies the subclass that the data controller will delete rows at the given index paths. - * - * @discussion This method will be performed before the data controller enters its editing queue. - * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or - * data stores before entering into editing the backing store on a background thread. - * - * @param indexPaths Index paths for the rows to be deleted. - */ -- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths; - -@end diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.h b/AsyncDisplayKit/Private/ASEnvironmentInternal.h deleted file mode 100644 index 7179830eef..0000000000 --- a/AsyncDisplayKit/Private/ASEnvironmentInternal.h +++ /dev/null @@ -1,79 +0,0 @@ -// -// ASEnvironmentInternal.h -// AsyncDisplayKit -// -// 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 - -#pragma once - -BOOL ASEnvironmentStatePropagationEnabled(); -BOOL ASEnvironmentStateTraitCollectionPropagationEnabled(); - - -#pragma mark - Set and get extensible values for layout options - -void _ASEnvironmentLayoutOptionsExtensionSetBoolAtIndex(id object, int idx, BOOL value); -BOOL _ASEnvironmentLayoutOptionsExtensionGetBoolAtIndex(id object, int idx); - -void _ASEnvironmentLayoutOptionsExtensionSetIntegerAtIndex(id object, int idx, NSInteger value); -NSInteger _ASEnvironmentLayoutOptionsExtensionGetIntegerAtIndex(id object, int idx); - -void _ASEnvironmentLayoutOptionsExtensionSetEdgeInsetsAtIndex(id object, int idx, UIEdgeInsets value); -UIEdgeInsets _ASEnvironmentLayoutOptionsExtensionGetEdgeInsetsAtIndex(id object, int idx); - - -#pragma mark - Traversing an ASEnvironment Tree - -void ASEnvironmentPerformBlockOnObjectAndChildren(id object, void(^block)(id object)); -void ASEnvironmentPerformBlockOnObjectAndParents(id object, void(^block)(id object)); - - -#pragma mark - - -enum class ASEnvironmentStatePropagation { DOWN, UP }; - - -#pragma mark - Merging - -static const struct ASEnvironmentStateExtensions ASEnvironmentDefaultStateExtensions = {}; - -static const struct ASEnvironmentLayoutOptionsState ASEnvironmentDefaultLayoutOptionsState = ASEnvironmentLayoutOptionsStateMakeDefault(); -ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentLayoutOptionsState state, ASEnvironmentStatePropagation propagation); - -static const struct ASEnvironmentHierarchyState ASEnvironmentDefaultHierarchyState = ASEnvironmentHierarchyStateMakeDefault(); -ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentHierarchyState state, ASEnvironmentStatePropagation propagation); - -static const struct ASEnvironmentTraitCollection ASEnvironmentDefaultTraitCollection = ASEnvironmentTraitCollectionMakeDefault(); -ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentTraitCollection state, ASEnvironmentStatePropagation propagation); - - -#pragma mark - Propagation - -template -void ASEnvironmentStatePropagateDown(id object, ASEnvironmentStateType state) { - ASEnvironmentPerformBlockOnObjectAndChildren(object, ^(id node) { - node.environmentState = ASEnvironmentMergeObjectAndState(node.environmentState, state, ASEnvironmentStatePropagation::DOWN); - }); -} - -template -void ASEnvironmentStatePropagateUp(id object, ASEnvironmentStateType state) { - ASEnvironmentPerformBlockOnObjectAndParents(object, ^(id node) { - node.environmentState = ASEnvironmentMergeObjectAndState(node.environmentState, state, ASEnvironmentStatePropagation::UP); - }); -} - -template -void ASEnvironmentStateApply(id object, ASEnvironmentStateType& state, ASEnvironmentStatePropagation propagate) { - if (propagate == ASEnvironmentStatePropagation::DOWN) { - ASEnvironmentStatePropagateUp(object, state); - } else if (propagate == ASEnvironmentStatePropagation::UP) { - ASEnvironmentStatePropagateDown(object, state); - } -} diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm deleted file mode 100644 index 99bed04aa8..0000000000 --- a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm +++ /dev/null @@ -1,217 +0,0 @@ -// -// ASEnvironmentInternal.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import "ASEnvironmentInternal.h" - -#import - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -#define AS_SUPPORT_PROPAGATION YES -#define AS_DOES_NOT_SUPPORT_PROPAGATION NO - -BOOL ASEnvironmentStatePropagationEnabled() -{ - return AS_DOES_NOT_SUPPORT_PROPAGATION; -} - -BOOL ASEnvironmentStateTraitCollectionPropagationEnabled() -{ - return AS_SUPPORT_PROPAGATION; -} - -#pragma mark - Traversing an ASEnvironment Tree - -void ASEnvironmentPerformBlockOnObjectAndChildren(id object, void(^block)(id node)) -{ - if (!object) { - return; - } - - std::queue> queue; - queue.push(object); - - while (!queue.empty()) { - id object = queue.front(); queue.pop(); - - block(object); - - for (id child in [object children]) { - queue.push(child); - } - } -} - -void ASEnvironmentPerformBlockOnObjectAndParents(id object, void(^block)(id node)) -{ - while (object) { - block(object); - object = [object parent]; - } -} - - -#pragma mark - Set and get extensible values from state structs - -void _ASEnvironmentLayoutOptionsExtensionSetBoolAtIndex(id object, int idx, BOOL value) -{ - NSCAssert(idx < kMaxEnvironmentStateBoolExtensions, @"Setting index outside of max bool extensions space"); - - ASEnvironmentState state = object.environmentState; - state.layoutOptionsState._extensions.boolExtensions[idx] = value; - object.environmentState = state; -} - -BOOL _ASEnvironmentLayoutOptionsExtensionGetBoolAtIndex(id object, int idx) -{ - NSCAssert(idx < kMaxEnvironmentStateBoolExtensions, @"Accessing index outside of max bool extensions space"); - return object.environmentState.layoutOptionsState._extensions.boolExtensions[idx]; -} - -void _ASEnvironmentLayoutOptionsExtensionSetIntegerAtIndex(id object, int idx, NSInteger value) -{ - NSCAssert(idx < kMaxEnvironmentStateIntegerExtensions, @"Setting index outside of max integer extensions space"); - - ASEnvironmentState state = object.environmentState; - state.layoutOptionsState._extensions.integerExtensions[idx] = value; - object.environmentState = state; -} - -NSInteger _ASEnvironmentLayoutOptionsExtensionGetIntegerAtIndex(id object, int idx) -{ - NSCAssert(idx < kMaxEnvironmentStateIntegerExtensions, @"Accessing index outside of max integer extensions space"); - return object.environmentState.layoutOptionsState._extensions.integerExtensions[idx]; -} - -void _ASEnvironmentLayoutOptionsExtensionSetEdgeInsetsAtIndex(id object, int idx, UIEdgeInsets value) -{ - NSCAssert(idx < kMaxEnvironmentStateEdgeInsetExtensions, @"Setting index outside of max edge insets extensions space"); - - ASEnvironmentState state = object.environmentState; - state.layoutOptionsState._extensions.edgeInsetsExtensions[idx] = value; - object.environmentState = state; -} - -UIEdgeInsets _ASEnvironmentLayoutOptionsExtensionGetEdgeInsetsAtIndex(id object, int idx) -{ - NSCAssert(idx < kMaxEnvironmentStateEdgeInsetExtensions, @"Accessing index outside of max edge insets extensions space"); - return object.environmentState.layoutOptionsState._extensions.edgeInsetsExtensions[idx]; -} - - -#pragma mark - Merging functions for states - -ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentHierarchyState hierarchyState, ASEnvironmentStatePropagation propagation) { - // Merge object and hierarchy state - LOG(@"Merge object and state: %@ - ASEnvironmentHierarchyState", hierarchyState); - return environmentState; -} - -ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentLayoutOptionsState layoutOptionsState, ASEnvironmentStatePropagation propagation) { - // Merge object and layout options state - LOG(@"Merge object and state: %@ - ASEnvironmentLayoutOptionsState", layoutOptionsState); - - if (!ASEnvironmentStatePropagationEnabled() && propagation == ASEnvironmentStatePropagation::UP) { - return environmentState; - } - - // Support propagate up - if (propagation == ASEnvironmentStatePropagation::UP) { - - // Object is the parent and the state is the state of the child - const ASEnvironmentLayoutOptionsState defaultState = ASEnvironmentLayoutOptionsStateMakeDefault(); - ASEnvironmentLayoutOptionsState parentLayoutOptionsState = environmentState.layoutOptionsState; - - // For every field check if the parent value is equal to the default and if so propegate up the value of the passed - // in layout options state - if (parentLayoutOptionsState.spacingBefore == defaultState.spacingBefore) { - parentLayoutOptionsState.spacingBefore = layoutOptionsState.spacingBefore; - } - if (parentLayoutOptionsState.spacingAfter == defaultState.spacingAfter) { - parentLayoutOptionsState.spacingAfter = layoutOptionsState.spacingAfter; - } - if (parentLayoutOptionsState.alignSelf == defaultState.alignSelf) { - parentLayoutOptionsState.alignSelf = layoutOptionsState.alignSelf; - } - if (parentLayoutOptionsState.flexGrow == defaultState.flexGrow) { - parentLayoutOptionsState.flexGrow = layoutOptionsState.flexGrow; - } - if (parentLayoutOptionsState.flexShrink == defaultState.flexShrink) { - parentLayoutOptionsState.flexShrink = layoutOptionsState.flexShrink; - } - if (ASDimensionEqualToDimension(parentLayoutOptionsState.flexBasis, defaultState.flexBasis)) { - parentLayoutOptionsState.flexBasis = layoutOptionsState.flexBasis; - } - if (parentLayoutOptionsState.alignSelf == defaultState.alignSelf) { - parentLayoutOptionsState.alignSelf = layoutOptionsState.alignSelf; - } - if (parentLayoutOptionsState.ascender == defaultState.ascender) { - parentLayoutOptionsState.ascender = layoutOptionsState.ascender; - } - if (parentLayoutOptionsState.descender == defaultState.descender) { - parentLayoutOptionsState.descender = layoutOptionsState.descender; - } - - if (CGPointEqualToPoint(parentLayoutOptionsState.layoutPosition, defaultState.layoutPosition)) { - // For now it is unclear if we should be up-propagating sizeRange or layoutPosition. - // parentLayoutOptionsState.layoutPosition = layoutOptionsState.layoutPosition; - } - - // Merge extended values if necessary - const ASEnvironmentStateExtensions defaultExtensions = ASEnvironmentDefaultStateExtensions; - const ASEnvironmentStateExtensions layoutOptionsStateExtensions = layoutOptionsState._extensions; - ASEnvironmentStateExtensions parentLayoutOptionsExtensions = parentLayoutOptionsState._extensions; - - for (int i = 0; i < kMaxEnvironmentStateBoolExtensions; i++) { - if (parentLayoutOptionsExtensions.boolExtensions[i] == defaultExtensions.boolExtensions[i]) { - parentLayoutOptionsExtensions.boolExtensions[i] = layoutOptionsStateExtensions.boolExtensions[i]; - } - } - - for (int i = 0; i < kMaxEnvironmentStateIntegerExtensions; i++) { - if (parentLayoutOptionsExtensions.integerExtensions[i] == defaultExtensions.integerExtensions[i]) { - parentLayoutOptionsExtensions.integerExtensions[i] = layoutOptionsStateExtensions.integerExtensions[i]; - } - } - - for (int i = 0; i < kMaxEnvironmentStateEdgeInsetExtensions; i++) { - if (UIEdgeInsetsEqualToEdgeInsets(parentLayoutOptionsExtensions.edgeInsetsExtensions[i], defaultExtensions.edgeInsetsExtensions[i])) { - parentLayoutOptionsExtensions.edgeInsetsExtensions[i] = layoutOptionsStateExtensions.edgeInsetsExtensions[i]; - } - } - parentLayoutOptionsState._extensions = parentLayoutOptionsExtensions; - - // Update layout options state - environmentState.layoutOptionsState = parentLayoutOptionsState; - } - - return environmentState; -} - -ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState childEnvironmentState, ASEnvironmentTraitCollection parentTraitCollection, ASEnvironmentStatePropagation propagation) { - if (propagation == ASEnvironmentStatePropagation::DOWN && !ASEnvironmentStateTraitCollectionPropagationEnabled()) { - return childEnvironmentState; - } - - // Support propagate down - if (propagation == ASEnvironmentStatePropagation::DOWN) { - ASEnvironmentTraitCollection childTraitCollection = childEnvironmentState.environmentTraitCollection; - childTraitCollection.horizontalSizeClass = parentTraitCollection.horizontalSizeClass; - childTraitCollection.verticalSizeClass = parentTraitCollection.verticalSizeClass; - childTraitCollection.userInterfaceIdiom = parentTraitCollection.userInterfaceIdiom; - childTraitCollection.forceTouchCapability = parentTraitCollection.forceTouchCapability; - childTraitCollection.displayScale = parentTraitCollection.displayScale; - childTraitCollection.containerSize = parentTraitCollection.containerSize; - childEnvironmentState.environmentTraitCollection = childTraitCollection; - - } - return childEnvironmentState; -} diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h deleted file mode 100644 index eb2ab924e9..0000000000 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h +++ /dev/null @@ -1,80 +0,0 @@ -// -// ASMultidimensionalArrayUtils.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import - -#import - - -/** - * Helper class for operation on multidimensional array, where the object of array may be an object or an array. - */ - -ASDISPLAYNODE_EXTERN_C_BEGIN - -/** - * Deep mutable copy of an array that contains arrays, which contain objects. It will go one level deep into the array to copy. - * This method is substantially faster than the generalized version, e.g. about 10x faster, so use it whenever it fits the need. - */ -extern NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) AS_WARN_UNUSED_RESULT; - -/** - * Deep mutable copy of multidimensional array. This is completely generalized and supports copying mixed-depth arrays, - * where some subarrays might contain both elements and other subarrays. It will recursively do the multiple copy for each subarray. - */ -extern NSObject *ASMultidimensionalArrayDeepMutableCopy(NSObject *obj) AS_WARN_UNUSED_RESULT; - -/** - * Insert the elements into the mutable multidimensional array at given index paths. - */ -extern void ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths, NSArray *elements); - -/** - * Delete the elements of the mutable multidimensional array at given index paths - */ -extern void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths); - -/** - * Find the elements of the mutable multidimensional array at given index paths. - */ -extern NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) AS_WARN_UNUSED_RESULT; - -/** - * Return all the index paths of mutable multidimensional array at given index set, in ascending order. - */ -extern NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensionalArray, NSIndexSet *indexSet) AS_WARN_UNUSED_RESULT; - -/** - * Moves the object at `sourceIndexPath` to `destinationIndexPath`. - */ -extern void ASMoveElementInTwoDimensionalArray(NSMutableArray *mutableArray, NSIndexPath *sourceIndexPath, NSIndexPath *destinationIndexPath); - -/** - * Return the index paths of the given multidimensional array that are present in the given index paths array. - */ -extern NSArray *ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths(NSArray *multidimensionalArray, NSArray *indexPaths) AS_WARN_UNUSED_RESULT; - -/** - * Return all the index paths of a two-dimensional array, in ascending order. - */ -extern NSArray *ASIndexPathsForTwoDimensionalArray(NSArray * twoDimensionalArray) AS_WARN_UNUSED_RESULT; - -/** - * Return all the index paths of a multidimensional array, in ascending order. - */ -extern NSArray *ASIndexPathsForMultidimensionalArray(NSArray *MultidimensionalArray) AS_WARN_UNUSED_RESULT; - -/** - * Attempt to get the object at the given index path. Returns @c nil if the index path is out of bounds. - */ -extern id ASGetElementInTwoDimensionalArray(NSArray *array, NSIndexPath *indexPath) AS_WARN_UNUSED_RESULT; - - -ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm deleted file mode 100644 index a36267474e..0000000000 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm +++ /dev/null @@ -1,241 +0,0 @@ -// -// ASMultidimensionalArrayUtils.mm -// AsyncDisplayKit -// -// 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 "ASAssert.h" -#import "ASMultidimensionalArrayUtils.h" - -// Import UIKit to get [NSIndexPath indexPathForItem:inSection:] which uses -// tagged pointers. -#import - -#pragma mark - Internal Methods - -static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, - const NSArray *indexPaths, - NSUInteger &curIdx, - NSIndexPath *curIndexPath, - const NSUInteger dimension, - void (^updateBlock)(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx)) -{ - if (curIdx == indexPaths.count) { - return; - } - - if (curIndexPath.length < dimension - 1) { - NSInteger i = 0; - for (NSMutableArray *subarray in mutableArray) { - ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(subarray, indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock); - i += 1; - } - } else { - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init]; - - while (curIdx < indexPaths.count && - [curIndexPath isEqual:[indexPaths[curIdx] indexPathByRemovingLastIndex]]) { - [indexSet addIndex:[indexPaths[curIdx] indexAtPosition:curIndexPath.length]]; - curIdx++; - } - - if (updateBlock){ - updateBlock(mutableArray, indexSet, curIdx); - } - } -} - -static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, NSIndexPath *curIndexPath, NSMutableArray *res) -{ - if (![obj isKindOfClass:[NSArray class]]) { - [res addObject:curIndexPath]; - } else { - NSArray *array = (NSArray *)obj; - NSUInteger idx = 0; - for (NSArray *subarray in array) { - ASRecursivelyFindIndexPathsForMultidimensionalArray(subarray, [curIndexPath indexPathByAddingIndex:idx], res); - idx++; - } - } -} - -static BOOL ASElementExistsAtIndexPathForMultidimensionalArray(NSArray *array, NSIndexPath *indexPath) { - NSUInteger indexLength = indexPath.length; - ASDisplayNodeCAssert(indexLength != 0, @"Must have a non-zero indexPath length"); - NSUInteger firstIndex = [indexPath indexAtPosition:0]; - BOOL elementExists = firstIndex < array.count; - - if (indexLength == 1) { - return elementExists; - } - - if (!elementExists) { - return NO; - } - - NSUInteger indexesLength = indexLength - 1; - NSUInteger indexes[indexesLength]; - [indexPath getIndexes:indexes range:NSMakeRange(1, indexesLength)]; - NSIndexPath *indexPathByRemovingFirstIndex; - // Use -indexPathForItem:inSection: if possible because it does not allocate into the heap - if (indexesLength == 2) { - indexPathByRemovingFirstIndex = [NSIndexPath indexPathForItem:indexes[1] inSection:indexes[0]]; - } else { - indexPathByRemovingFirstIndex = [NSIndexPath indexPathWithIndexes:indexes length:indexesLength]; - } - - return ASElementExistsAtIndexPathForMultidimensionalArray(array[firstIndex], indexPathByRemovingFirstIndex); -} - -#pragma mark - Public Methods - -NSObject *ASMultidimensionalArrayDeepMutableCopy(NSObject *obj) -{ - if ([obj isKindOfClass:[NSArray class]]) { - NSArray *arr = (NSArray *)obj; - NSMutableArray * mutableArr = [NSMutableArray arrayWithCapacity:arr.count]; - for (NSObject *elem in arr) { - [mutableArr addObject:ASMultidimensionalArrayDeepMutableCopy(elem)]; - } - return mutableArr; - } - - return obj; -} - -NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) -{ - NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; - for (NSArray *subarray in array) { - ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); - [newArray addObject:[subarray mutableCopy]]; - } - return newArray; -} - -void ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths, NSArray *elements) -{ - ASDisplayNodeCAssert(indexPaths.count == elements.count, @"Inconsistent indexPaths and elements"); - - if (!indexPaths.count) { - return; - } - - NSUInteger curIdx = 0; - NSIndexPath *indexPath = [[NSIndexPath alloc] init]; - ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray, indexPaths, curIdx, indexPath, [indexPaths[0] length], ^(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx) { - [arr insertObjects:[elements subarrayWithRange:NSMakeRange(idx - indexSet.count, indexSet.count)] - atIndexes:indexSet]; - }); - - ASDisplayNodeCAssert(curIdx == indexPaths.count, @"Indexpath is out of range !"); -} - -void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) -{ - if (!indexPaths.count) { - return; - } - - NSUInteger curIdx = 0; - NSIndexPath *indexPath = [[NSIndexPath alloc] init]; - - ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray, indexPaths, curIdx, indexPath, [indexPaths[0] length], ^(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx) { - [arr removeObjectsAtIndexes:indexSet]; - }); - - ASDisplayNodeCAssert(curIdx == indexPaths.count, @"Indexpath is out of range !"); -} - -NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) -{ - NSUInteger curIdx = 0; - NSIndexPath *indexPath = [[NSIndexPath alloc] init]; - NSMutableArray *deletedElements = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - - if (!indexPaths.count) { - return deletedElements; - } - - ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray, indexPaths, curIdx, indexPath, [indexPaths[0] length], ^(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx) { - [deletedElements addObjectsFromArray:[arr objectsAtIndexes:indexSet]]; - }); - - ASDisplayNodeCAssert(curIdx == indexPaths.count, @"Indexpath is out of range !"); - - return deletedElements; -} - -NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensionalArray, NSIndexSet *indexSet) -{ - NSMutableArray *res = [NSMutableArray array]; - [indexSet enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger i = range.location; i < NSMaxRange(range); i++) { - ASRecursivelyFindIndexPathsForMultidimensionalArray(multidimensionalArray[i], [NSIndexPath indexPathWithIndex:i], res); - } - }]; - - return res; -} - -void ASMoveElementInTwoDimensionalArray(NSMutableArray *mutableArray, NSIndexPath *sourceIndexPath, NSIndexPath *destinationIndexPath) -{ - NSMutableArray *oldSection = mutableArray[sourceIndexPath.section]; - NSInteger oldItem = sourceIndexPath.item; - id object = oldSection[oldItem]; - [oldSection removeObjectAtIndex:oldItem]; - [mutableArray[destinationIndexPath.section] insertObject:object atIndex:destinationIndexPath.item]; -} - -NSArray *ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths(NSArray *multidimensionalArray, NSArray *indexPaths) -{ - NSMutableArray *res = [NSMutableArray array]; - for (NSIndexPath *indexPath in indexPaths) { - if (ASElementExistsAtIndexPathForMultidimensionalArray(multidimensionalArray, indexPath)) { - [res addObject:indexPath]; - } - } - - return res; -} - -NSArray *ASIndexPathsForTwoDimensionalArray(NSArray * twoDimensionalArray) -{ - NSMutableArray *result = [NSMutableArray array]; - NSUInteger section = 0; - for (NSArray *subarray in twoDimensionalArray) { - ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); - for (NSUInteger item = 0; item < subarray.count; item++) { - [result addObject:[NSIndexPath indexPathForItem:item inSection:section]]; - } - section++; - } - return result; -} - -NSArray *ASIndexPathsForMultidimensionalArray(NSArray *multidimensionalArray) -{ - NSMutableArray *res = [NSMutableArray array]; - ASRecursivelyFindIndexPathsForMultidimensionalArray(multidimensionalArray, [[NSIndexPath alloc] init], res); - return res; -} - -id ASGetElementInTwoDimensionalArray(NSArray *array, NSIndexPath *indexPath) -{ - ASDisplayNodeCAssert(indexPath.length == 2, @"Expected index path of length 2. Index path: %@", indexPath); - NSInteger section = indexPath.section; - if (array.count <= section) { - return nil; - } - - NSArray *innerArray = array[section]; - NSInteger item = indexPath.item; - if (innerArray.count <= item) { - return nil; - } - return innerArray[item]; -} diff --git a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.h b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.h deleted file mode 100644 index 9764c25d12..0000000000 --- a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// ASStackBaselinePositionedLayout.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import "ASLayout.h" -#import "ASDimension.h" -#import "ASStackPositionedLayout.h" - -struct ASStackBaselinePositionedLayout { - const std::vector items; - const CGFloat crossSize; - const CGFloat ascender; - const CGFloat descender; - - /** Given a positioned layout, computes each child position using baseline alignment. */ - static ASStackBaselinePositionedLayout compute(const ASStackPositionedLayout &positionedLayout, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &constrainedSize); - - static BOOL needsBaselineAlignment(const ASStackLayoutSpecStyle &style); -}; diff --git a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm deleted file mode 100644 index 3dad841291..0000000000 --- a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm +++ /dev/null @@ -1,172 +0,0 @@ -// -// ASStackBaselinePositionedLayout.mm -// AsyncDisplayKit -// -// 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 "ASStackBaselinePositionedLayout.h" - -#import "ASLayoutSpecUtilities.h" -#import "ASLayoutSpec+Subclasses.h" - -#import "ASLayoutElement.h" -#import "ASLayoutElementStylePrivate.h" - -static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecItem &l) -{ - switch (style.alignItems) { - case ASStackLayoutAlignItemsBaselineFirst: - return l.child.style.ascender; - case ASStackLayoutAlignItemsBaselineLast: - return l.layout.size.height + l.child.style.descender; - default: - return 0; - } -} - -static CGFloat baselineOffset(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecItem &l, - const CGFloat maxAscender, - const CGFloat maxBaseline) -{ - if (style.direction == ASStackLayoutDirectionHorizontal) { - switch (style.alignItems) { - case ASStackLayoutAlignItemsBaselineFirst: - return maxAscender - l.child.style.ascender; - case ASStackLayoutAlignItemsBaselineLast: - return maxBaseline - baselineForItem(style, l); - - default: - return 0; - } - } - return 0; -} - -static CGFloat maxDimensionForItem(const ASStackLayoutSpecItem &l, - const ASStackLayoutSpecStyle &style) -{ - CGFloat maxDimension = crossDimension(style.direction, l.layout.size); - style.direction == ASStackLayoutDirectionVertical ? maxDimension += l.layout.position.x : maxDimension += l.layout.position.y; - return maxDimension; -} - -BOOL ASStackBaselinePositionedLayout::needsBaselineAlignment(const ASStackLayoutSpecStyle &style) -{ - return style.baselineRelativeArrangement || - style.alignItems == ASStackLayoutAlignItemsBaselineFirst || - style.alignItems == ASStackLayoutAlignItemsBaselineLast; -} - -ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const ASStackPositionedLayout &positionedLayout, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &constrainedSize) -{ - const auto stackedChildren = positionedLayout.items; - - /* Step 1: Look at each child and determine the distance from the top of the child node to its baseline. - For example, let's say we have the following two text nodes and want to align them to the first baseline: - - Hello! Why, hello there! How - are you today? - - The first node has a font of size 14, the second a font of size 12. The first node will have a baseline offset of - the ascender of a font of size 14, the second will have a baseline of the ascender of a font of size 12. The first - baseline will be larger so we will keep that as the max baseline. - - However, if were to align from the last baseline we'd find the max baseline by taking the height of node and adding - the font's descender (it's negative). In the case of the first node, which is only 1 line, this should be the same value as the ascender. - The second node, however, has a larger height and there will have a larger baseline offset. - */ - const auto baselineIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ - return baselineForItem(style, a) < baselineForItem(style, b); - }); - const CGFloat maxBaseline = baselineIt == stackedChildren.end() ? 0 : baselineForItem(style, *baselineIt); - - /* - Step 2: Find the max ascender for all of the children. - Imagine 3 nodes aligned horizontally, all with the same text but with font sizes of 12, 14, 16. Because it is has the largest - ascender node with font size of 16 will not need to move, the other two nodes will align to this node's baseline. The offset we will use - for each node is our computed maxAscender - node.ascender. If the 16pt node had an ascender of 10 and the 14pt node - had an ascender of 8, that means we will offset the 14pt node by 2 pts. - - Note: if we are aligning to the last baseline, then we don't need this value in our computation. However, we do want - our layoutSpec to have it so that it can be baseline aligned with another text node or baseline layout spec. - */ - const auto ascenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ - return a.child.style.ascender < b.child.style.ascender; - }); - const CGFloat maxAscender = ascenderIt == stackedChildren.end() ? 0 : (*ascenderIt).child.style.ascender; - - /* - Step 3: Take each child and update its layout position based on the baseline offset. - - If this is a horizontal stack, we take a positioned child and add to its y offset to align it to the maxBaseline of the children. - If this is a vertical stack, we add the child's descender to the location of the next child to position. This will ensure the - spacing between the two nodes is from the baseline, not the bounding box. - - */ - - // Only change positions of layouts this stackSpec is aligning to a baseline. Otherwise we are only here to - // compute the min/max descender/ascender for this stack spec. - if (ASStackBaselinePositionedLayout::needsBaselineAlignment(style)) { - // Adjust the positioned layout items to be positioned based on the baseline - CGPoint p = CGPointZero; - BOOL first = YES; - - for (const ASStackLayoutSpecItem &l : stackedChildren) { - ASLayoutElementStyle *layoutElementStyle = l.child.style; - - p = p + directionPoint(style.direction, layoutElementStyle.spacingBefore, 0); - - // if this is the first item use the previously computed start point otherwise add the stack spacing - p = first ? l.layout.position : p + directionPoint(style.direction, style.spacing, 0); - first = NO; - - // Find the difference between this node's baseline and the max baseline of all the children. Add this difference to the child's y position. - l.layout.position = p + CGPointMake(0, baselineOffset(style, l, maxAscender, maxBaseline)); - - // If we are a vertical stack, add the item's descender (it is negative) to the offset for the next node. This will ensure we are spacing - // node from baselines and not bounding boxes. - CGFloat spacingAfterBaseline = 0; - if (style.direction == ASStackLayoutDirectionVertical) { - spacingAfterBaseline = layoutElementStyle.descender; - } - p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + layoutElementStyle.spacingAfter + spacingAfterBaseline, 0); - } - } - - /* - Step 4: Since we have been mucking with positions, there is a chance that our cross size has changed. Imagine a node with a font size of 40 - and another node with a font size of 12 but with multiple lines. We align these nodes to the first baseline, which will be the baseline of the node with - font size of 40 (max ascender). Now, we have to move the node with multiple lines down to the other node's baseline. This node with multiple lines will - extend below the first node farther than it did before aligning the baselines thus increasing the cross size. - - After finding the new cross size, we need to clamp it so that it fits within the constrained size. - - */ - const auto it = std::max_element(stackedChildren.begin(), stackedChildren.end(), - [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b) { - return maxDimensionForItem(a, style) < maxDimensionForItem(b, style); - }); - const auto largestChildCrossSize = it == stackedChildren.end() ? 0 : maxDimensionForItem(*it, style); - const auto minCrossSize = crossDimension(style.direction, constrainedSize.min); - const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max); - const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize); - - /* - Step 5: finally, we must find the smallest descender (descender is negative). This is since ASBaselineLayoutSpec implements - ASLayoutElement and needs an ascender and descender to lay itself out properly. - */ - const auto descenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ - return a.layout.position.y + a.layout.size.height < b.layout.position.y + b.layout.size.height; - }); - const CGFloat minDescender = descenderIt == stackedChildren.end() ? 0 : (*descenderIt).child.style.descender; - - return {std::move(stackedChildren), crossSize, maxAscender, minDescender}; -} diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm deleted file mode 100644 index 1fdef8eb0b..0000000000 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ /dev/null @@ -1,131 +0,0 @@ -// -// ASStackPositionedLayout.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import "ASStackPositionedLayout.h" - -#import - -#import "ASInternalHelpers.h" -#import "ASLayoutSpecUtilities.h" -#import "ASLayoutSpec+Subclasses.h" - -static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecItem &l, - const CGFloat crossSize) -{ - switch (alignment(l.child.style.alignSelf, style.alignItems)) { - case ASStackLayoutAlignItemsEnd: - return crossSize - crossDimension(style.direction, l.layout.size); - case ASStackLayoutAlignItemsCenter: - return ASFloorPixelValue((crossSize - crossDimension(style.direction, l.layout.size)) / 2); - case ASStackLayoutAlignItemsBaselineFirst: - case ASStackLayoutAlignItemsBaselineLast: - case ASStackLayoutAlignItemsStart: - case ASStackLayoutAlignItemsStretch: - return 0; - } -} - -/** - * Positions children according to the stack style and positioning properties. - * - * @param style The layout style of the overall stack layout - * @param firstChildOffset Offset of the first child - * @param extraSpacing Spacing between children, in addition to spacing set to the stack's layout style - * @param lastChildOffset Offset of the last child - * @param unpositionedLayout Unpositioned children of the stack - * @param constrainedSize Constrained size of the stack - */ -static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style, - const CGFloat firstChildOffset, - const CGFloat extraSpacing, - const CGFloat lastChildOffset, - const ASStackUnpositionedLayout &unpositionedLayout, - const ASSizeRange &constrainedSize) -{ - - // The cross dimension is the max of the childrens' cross dimensions (clamped to our constraint below). - const auto it = std::max_element(unpositionedLayout.items.begin(), unpositionedLayout.items.end(), - [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ - return compareCrossDimension(style.direction, a.layout.size, b.layout.size); - }); - const auto largestChildCrossSize = it == unpositionedLayout.items.end() ? 0 : crossDimension(style.direction, it->layout.size); - const auto minCrossSize = crossDimension(style.direction, constrainedSize.min); - const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max); - const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize); - - CGPoint p = directionPoint(style.direction, firstChildOffset, 0); - BOOL first = YES; - const auto lastChild = unpositionedLayout.items.back().child; - CGFloat offset = 0; - - // Adjust the position of the unpositioned layouts to be positioned - const auto stackedChildren = unpositionedLayout.items; - for (const auto &l : stackedChildren) { - offset = (l.child.element == lastChild.element) ? lastChildOffset : 0; - p = p + directionPoint(style.direction, l.child.style.spacingBefore + offset, 0); - if (!first) { - p = p + directionPoint(style.direction, style.spacing + extraSpacing, 0); - } - first = NO; - l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize)); - - p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.style.spacingAfter, 0); - } - - return {std::move(stackedChildren), crossSize}; -} - -static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style, - const CGFloat firstChildOffset, - const ASStackUnpositionedLayout &unpositionedLayout, - const ASSizeRange &constrainedSize) -{ - return stackedLayout(style, firstChildOffset, 0, 0, unpositionedLayout, constrainedSize); -} - -ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &constrainedSize) -{ - const auto numOfItems = unpositionedLayout.items.size(); - ASDisplayNodeCAssertTrue(numOfItems > 0); - const CGFloat violation = unpositionedLayout.violation; - ASStackLayoutJustifyContent justifyContent = style.justifyContent; - - // Handle edge cases of "space between" and "space around" - if (justifyContent == ASStackLayoutJustifyContentSpaceBetween && (violation < 0 || numOfItems == 1)) { - justifyContent = ASStackLayoutJustifyContentStart; - } else if (justifyContent == ASStackLayoutJustifyContentSpaceAround && (violation < 0 || numOfItems == 1)) { - justifyContent = ASStackLayoutJustifyContentCenter; - } - - switch (justifyContent) { - case ASStackLayoutJustifyContentStart: { - return stackedLayout(style, 0, unpositionedLayout, constrainedSize); - } - case ASStackLayoutJustifyContentCenter: { - return stackedLayout(style, std::floor(violation / 2), unpositionedLayout, constrainedSize); - } - case ASStackLayoutJustifyContentEnd: { - return stackedLayout(style, violation, unpositionedLayout, constrainedSize); - } - case ASStackLayoutJustifyContentSpaceBetween: { - // Spacing between the items, no spaces at the edges, evenly distributed - const auto numOfSpacings = numOfItems - 1; - return stackedLayout(style, 0, violation / numOfSpacings, 0, unpositionedLayout, constrainedSize); - } - case ASStackLayoutJustifyContentSpaceAround: { - // Spacing between items are twice the spacing on the edges - CGFloat spacingUnit = violation / (numOfItems * 2); - return stackedLayout(style, spacingUnit, spacingUnit * 2, 0, unpositionedLayout, constrainedSize); - } - } -} diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h deleted file mode 100644 index c1607c93cb..0000000000 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// ASStackUnpositionedLayout.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import - -#import "ASLayout.h" -#import "ASStackLayoutSpecUtilities.h" -#import "ASStackLayoutSpec.h" - -struct ASStackLayoutSpecChild { - /** The original source child. */ - id element; - /** Style object of element. */ - ASLayoutElementStyle *style; - /** Size object of the element */ - ASLayoutElementSize size; -}; - -struct ASStackLayoutSpecItem { - /** The original source child. */ - ASStackLayoutSpecChild child; - /** The proposed layout or nil if no is calculated yet. */ - ASLayout *layout; -}; - - -/** Represents a set of stack layout children that have their final layout computed, but are not yet positioned. */ -struct ASStackUnpositionedLayout { - /** A set of proposed child layouts, not yet positioned. */ - const std::vector items; - /** The total size of the children in the stack dimension, including all spacing. */ - const CGFloat stackDimensionSum; - /** The amount by which stackDimensionSum violates constraints. If positive, less than min; negative, greater than max. */ - const CGFloat violation; - - /** Given a set of children, computes the unpositioned layouts for those children. */ - static ASStackUnpositionedLayout compute(const std::vector &children, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange); -}; diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm deleted file mode 100644 index f86d5ef4ae..0000000000 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm +++ /dev/null @@ -1,452 +0,0 @@ -// -// ASStackUnpositionedLayout.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import "ASStackUnpositionedLayout.h" - -#import -#import - -#import "ASLayoutSpecUtilities.h" -#import "ASLayoutElementStylePrivate.h" - -static CGFloat resolveCrossDimensionMaxForStretchChild(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecChild &child, - const CGFloat stackMax, - const CGFloat crossMax) -{ - // stretched children may have a cross direction max that is smaller than the minimum size constraint of the parent. - const CGFloat computedMax = (style.direction == ASStackLayoutDirectionVertical ? - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.width : - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.height); - return computedMax == INFINITY ? crossMax : computedMax; -} - -static CGFloat resolveCrossDimensionMinForStretchChild(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecChild &child, - const CGFloat stackMax, - const CGFloat crossMin) -{ - // stretched children will have a cross dimension of at least crossMin, unless they explicitly define a child size - // that is smaller than the constraint of the parent. - return (style.direction == ASStackLayoutDirectionVertical ? - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).min.width : - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).min.height) ?: crossMin; -} - -/** - Sizes the child given the parameters specified, and returns the computed layout. - */ -static ASLayout *crossChildLayout(const ASStackLayoutSpecChild &child, - const ASStackLayoutSpecStyle &style, - const CGFloat stackMin, - const CGFloat stackMax, - const CGFloat crossMin, - const CGFloat crossMax, - const CGSize size) -{ - const ASStackLayoutAlignItems alignItems = alignment(child.style.alignSelf, style.alignItems); - // stretched children will have a cross dimension of at least crossMin - const CGFloat childCrossMin = (alignItems == ASStackLayoutAlignItemsStretch ? - resolveCrossDimensionMinForStretchChild(style, child, stackMax, crossMin) : - 0); - const CGFloat childCrossMax = (alignItems == ASStackLayoutAlignItemsStretch ? - resolveCrossDimensionMaxForStretchChild(style, child, stackMax, crossMax) : - crossMax); - const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, childCrossMax); - ASLayout *layout = [child.element layoutThatFits:childSizeRange parentSize:size]; - ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from measureWithSizeRange: must not be nil: %@", child.element); - return layout ? : [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]; -} - -/** - Stretches children to lay out along the cross axis according to the alignment stretch settings of the children - (child.alignSelf), and the stack layout's alignment settings (style.alignItems). This does not do the actual alignment - of the items once stretched though; ASStackPositionedLayout will do centering etc. - - Finds the maximum cross dimension among child layouts. If that dimension exceeds the minimum cross layout size then - we must stretch any children whose alignItems specify ASStackLayoutAlignItemsStretch. - - The diagram below shows 3 children in a horizontal stack. The second child is larger than the minCrossDimension, so - its height is used as the childCrossMax. Any children that are stretchable (which may be all children if - style.alignItems specifies stretch) like the first child must be stretched to match that maximum. All children must be - at least minCrossDimension in cross dimension size, which is shown by the sizing of the third child. - - Stack Dimension - +---------------------> - + +-+-------------+-+-------------+--+---------------+ + + + - | | child. | | | | | | | | - | | alignSelf | | | | | | | | - Cross | | = stretch | | | +-------+-------+ | | | - Dimension | +-----+-------+ | | | | | | | | - | | | | | | | | | | - | | | | | v | | | | - v +-+- - - - - - -+-+ - - - - - - +--+- - - - - - - -+ | | + minCrossDimension - | | | | | - | v | | | | | - +- - - - - - -+ +-------------+ | + childCrossMax - | - +--------------------------------------------------+ + crossMax - - @param layouts pre-computed child layouts; modified in-place as needed - @param style the layout style of the overall stack layout - */ -static void stretchChildrenAlongCrossDimension(std::vector &layouts, - const ASStackLayoutSpecStyle &style, - const CGSize size) -{ - // Find the maximum cross dimension size among child layouts - const auto it = std::max_element(layouts.begin(), layouts.end(), - [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b) { - return compareCrossDimension(style.direction, a.layout.size, b.layout.size); - }); - - const CGFloat childCrossMax = it == layouts.end() ? 0 : crossDimension(style.direction, it->layout.size); - for (auto &l : layouts) { - const ASStackLayoutAlignItems alignItems = alignment(l.child.style.alignSelf, style.alignItems); - - const CGFloat cross = crossDimension(style.direction, l.layout.size); - const CGFloat stack = stackDimension(style.direction, l.layout.size); - - // restretch all stretchable children along the cross axis using the new min. set their max size to childCrossMax, - // not crossMax, so that if any of them would choose a larger size just because the min size increased (weird!) - // they are forced to choose the same width as all the other children. - if (alignItems == ASStackLayoutAlignItemsStretch && std::fabs(cross - childCrossMax) > 0.01) { - l.layout = crossChildLayout(l.child, style, stack, stack, childCrossMax, childCrossMax, size); - } - } -} - -/** The threshold that determines if a violation has actually occurred. */ -static const CGFloat kViolationEpsilon = 0.01; - -/** - Returns a lambda that computes the relevant flex factor based on the given violation. - @param violation The amount that the stack layout violates its size range. See header for sign interpretation. - */ -static std::function flexFactorInViolationDirection(const CGFloat violation) -{ - if (std::fabs(violation) < kViolationEpsilon) { - return [](const ASStackLayoutSpecItem &item) { return 0.0; }; - } else if (violation > 0) { - return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexGrow; }; - } else { - return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexShrink; }; - } -} - -static inline CGFloat scaledFlexShrinkFactor(const ASStackLayoutSpecItem &item, - const ASStackLayoutSpecStyle &style, - const CGFloat flexFactorSum) -{ - return stackDimension(style.direction, item.layout.size) * (item.child.style.flexShrink / flexFactorSum); -} - -/** - Returns a lambda that computes a flex shrink adjustment for a given item based on the provided violation. - @param items The unpositioned items from the original unconstrained layout pass. - @param style The layout style to be applied to all children. - @param violation The amount that the stack layout violates its size range. - @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. - @return A lambda capable of computing the flex shrink adjustment, if any, for a particular item. - */ -static std::function flexShrinkAdjustment(const std::vector &items, - const ASStackLayoutSpecStyle &style, - const CGFloat violation, - const CGFloat flexFactorSum) -{ - const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) { - return x + scaledFlexShrinkFactor(item, style, flexFactorSum); - }); - return [style, scaledFlexShrinkFactorSum, violation, flexFactorSum](const ASStackLayoutSpecItem &item) { - const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style, flexFactorSum) / scaledFlexShrinkFactorSum; - // The item should shrink proportionally to the scaled flex shrink factor ratio computed above. - // Unlike the flex grow adjustment the flex shrink adjustment needs to take the size of each item into account. - return -std::fabs(scaledFlexShrinkFactorRatio * violation); - }; -} - -/** - Returns a lambda that computes a flex grow adjustment for a given item based on the provided violation. - @param items The unpositioned items from the original unconstrained layout pass. - @param violation The amount that the stack layout violates its size range. - @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. - @return A lambda capable of computing the flex grow adjustment, if any, for a particular item. - */ -static std::function flexGrowAdjustment(const std::vector &items, - const CGFloat violation, - const CGFloat flexFactorSum) -{ - // To compute the flex grow adjustment distribute the violation proportionally based on each item's flex grow factor. - return [violation, flexFactorSum](const ASStackLayoutSpecItem &item) { - return std::floor(violation * (item.child.style.flexGrow / flexFactorSum)); - }; -} - -/** - Returns a lambda that computes a flex adjustment for a given item based on the provided violation. - @param items The unpositioned items from the original unconstrained layout pass. - @param style The layout style to be applied to all children. - @param violation The amount that the stack layout violates its size range. - @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. - @return A lambda capable of computing the flex adjustment for a particular item. - */ -static std::function flexAdjustmentInViolationDirection(const std::vector &items, - const ASStackLayoutSpecStyle &style, - const CGFloat violation, - const CGFloat flexFactorSum) -{ - if (violation > 0) { - return flexGrowAdjustment(items, violation, flexFactorSum); - } else { - return flexShrinkAdjustment(items, style, violation, flexFactorSum); - } -} - -ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(const ASStackLayoutSpecChild &child) -{ - return child.style.flexGrow > 0 && child.style.flexShrink > 0; -} - -/** - The flexible children may have been left not laid out in the initial layout pass, so we may have to go through and size - these children at zero size so that the children layouts are at least present. - */ -static void layoutFlexibleChildrenAtZeroSize(std::vector &items, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange, - const CGSize size) -{ - for (ASStackLayoutSpecItem &item : items) { - if (isFlexibleInBothDirections(item.child)) { - item.layout = crossChildLayout(item.child, - style, - 0, - 0, - crossDimension(style.direction, sizeRange.min), - crossDimension(style.direction, sizeRange.max), - size); - } - } -} - -/** - Computes the consumed stack dimension length for the given vector of children and stacking style. - - stackDimensionSum - <-----------------------> - +-----+ +-------+ +---+ - | | | | | | - | | | | | | - +-----+ | | +---+ - +-------+ - - @param children unpositioned layouts for the children of the stack spec - @param style the layout style of the overall stack layout - */ -static CGFloat computeStackDimensionSum(const std::vector &children, - const ASStackLayoutSpecStyle &style) -{ - // Sum up the childrens' spacing - const CGFloat childSpacingSum = std::accumulate(children.begin(), children.end(), - // Start from default spacing between each child: - children.empty() ? 0 : style.spacing * (children.size() - 1), - [&](CGFloat x, const ASStackLayoutSpecItem &l) { - return x + l.child.style.spacingBefore + l.child.style.spacingAfter; - }); - - // Sum up the childrens' dimensions (including spacing) in the stack direction. - const CGFloat childStackDimensionSum = std::accumulate(children.begin(), children.end(), childSpacingSum, - [&](CGFloat x, const ASStackLayoutSpecItem &l) { - return x + stackDimension(style.direction, l.layout.size); - }); - return childStackDimensionSum; -} - -/** - Computes the violation by comparing a stack dimension sum with the overall allowable size range for the stack. - - Violation is the distance you would have to add to the unbounded stack-direction length of the stack spec's - children in order to bring the stack within its allowed sizeRange. The diagram below shows 3 horizontal stacks with - the different types of violation. - - sizeRange - |------------| - +------+ +-------+ +-------+ +---------+ - | | | | | | | | | | - | | | | | | | | (zero violation) - | | | | | | | | | | - +------+ +-------+ +-------+ +---------+ - | | - +------+ +-------+ +-------+ - | | | | | | | | - | | | | | |<--> (positive violation) - | | | | | | | | - +------+ +-------+ +-------+ - | |<------> (negative violation) - +------+ +-------+ +-------+ +---------+ +-----------+ - | | | | | | | | | | | | - | | | | | | | | | | - | | | | | | | | | | | | - +------+ +-------+ +-------+ +---------+ +-----------+ - - @param stackDimensionSum the consumed length of the children in the stack along the stack dimension - @param style layout style to be applied to all children - @param sizeRange the range of allowable sizes for the stack layout spec - */ -static CGFloat computeViolation(const CGFloat stackDimensionSum, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - const CGFloat minStackDimension = stackDimension(style.direction, sizeRange.min); - const CGFloat maxStackDimension = stackDimension(style.direction, sizeRange.max); - if (stackDimensionSum < minStackDimension) { - return minStackDimension - stackDimensionSum; - } else if (stackDimensionSum > maxStackDimension) { - return maxStackDimension - stackDimensionSum; - } - return 0; -} - -/** - If we have a single flexible (both shrinkable and growable) child, and our allowed size range is set to a specific - number then we may avoid the first "intrinsic" size calculation. - */ -ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector &children, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - const NSUInteger flexibleChildren = std::count_if(children.begin(), children.end(), isFlexibleInBothDirections); - return ((flexibleChildren == 1) - && (stackDimension(style.direction, sizeRange.min) == - stackDimension(style.direction, sizeRange.max))); -} - -/** - Flexes children in the stack axis to resolve a min or max stack size violation. First, determines which children are - flexible (see computeViolation and isFlexibleInViolationDirection). Then computes how much to flex each flexible child - and performs re-layout. Note that there may still be a non-zero violation even after flexing. - - The actual CSS flexbox spec describes an iterative looping algorithm here, which may be adopted in t5837937: - http://www.w3.org/TR/css3-flexbox/#resolve-flexible-lengths - - @param items Reference to unpositioned items from the original, unconstrained layout pass; modified in-place - @param style layout style to be applied to all children - @param sizeRange the range of allowable sizes for the stack layout component - @param size Size of the stack layout component. May be undefined in either or both directions. - */ -static void flexChildrenAlongStackDimension(std::vector &items, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange, - const CGSize size, - const BOOL useOptimizedFlexing) -{ - const CGFloat violation = computeViolation(computeStackDimensionSum(items, style), style, sizeRange); - std::function flexFactor = flexFactorInViolationDirection(violation); - // The flex factor sum is needed to determine if flexing is necessary. - // This value is also needed if the violation is positive and flexible children need to grow, so keep it around. - const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) { - return x + flexFactor(item); - }); - // If no children are able to flex then there is nothing left to do. Bail. - if (flexFactorSum == 0) { - // If optimized flexing was used then we have to clean up the unsized children and lay them out at zero size. - if (useOptimizedFlexing) { - layoutFlexibleChildrenAtZeroSize(items, style, sizeRange, size); - } - return; - } - std::function flexAdjustment = flexAdjustmentInViolationDirection(items, - style, - violation, - flexFactorSum); - - // Compute any remaining violation to the first flexible child. - const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackLayoutSpecItem &item) { - return x - flexAdjustment(item); - }); - BOOL isFirstFlex = YES; - for (ASStackLayoutSpecItem &item : items) { - const CGFloat currentFlexAdjustment = flexAdjustment(item); - // Children are consider inflexible if they do not need to make a flex adjustment. - if (currentFlexAdjustment != 0) { - const CGFloat originalStackSize = stackDimension(style.direction, item.layout.size); - // Only apply the remaining violation for the first flexible child that has a flex grow factor. - const CGFloat flexedStackSize = originalStackSize + currentFlexAdjustment + (isFirstFlex && item.child.style.flexGrow > 0 ? remainingViolation : 0); - item.layout = crossChildLayout(item.child, - style, - MAX(flexedStackSize, 0), - MAX(flexedStackSize, 0), - crossDimension(style.direction, sizeRange.min), - crossDimension(style.direction, sizeRange.max), - size); - isFirstFlex = NO; - } - } -} - -/** - Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and - stretched. - */ -static std::vector layoutChildrenAlongUnconstrainedStackDimension(const std::vector &children, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange, - const CGSize size, - const BOOL useOptimizedFlexing) -{ - const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); - const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); - return AS::map(children, [&](const ASStackLayoutSpecChild &child) -> ASStackLayoutSpecItem { - if (useOptimizedFlexing && isFlexibleInBothDirections(child)) { - return {child, [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]}; - } else { - return { - child, - crossChildLayout(child, - style, - ASDimensionResolve(child.style.flexBasis, stackDimension(style.direction, size), 0), - ASDimensionResolve(child.style.flexBasis, stackDimension(style.direction, size), INFINITY), - minCrossDimension, - maxCrossDimension, - size) - }; - } - }); -} - -ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector &children, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - // If we have a fixed size in either dimension, pass it to children so they can resolve percentages against it. - // Otherwise, we pass ASLayoutElementParentDimensionUndefined since it will depend on the content. - const CGSize size = { - (sizeRange.min.width == sizeRange.max.width) ? sizeRange.min.width : ASLayoutElementParentDimensionUndefined, - (sizeRange.min.height == sizeRange.max.height) ? sizeRange.min.height : ASLayoutElementParentDimensionUndefined, - }; - - // We may be able to avoid some redundant layout passes - const BOOL optimizedFlexing = useOptimizedFlexing(children, style, sizeRange); - - // We do a first pass of all the children, generating an unpositioned layout for each with an unbounded range along - // the stack dimension. This allows us to compute the "intrinsic" size of each child and find the available violation - // which determines whether we must grow or shrink the flexible children. - std::vector items = layoutChildrenAlongUnconstrainedStackDimension(children, - style, - sizeRange, - size, - optimizedFlexing); - - flexChildrenAlongStackDimension(items, style, sizeRange, size, optimizedFlexing); - stretchChildrenAlongCrossDimension(items, style, size); - - const CGFloat stackDimensionSum = computeStackDimensionSum(items, style); - return {std::move(items), stackDimensionSum, computeViolation(stackDimensionSum, style, sizeRange)}; -} diff --git a/AsyncDisplayKitTests/ASPagerNodeTests.m b/AsyncDisplayKitTests/ASPagerNodeTests.m deleted file mode 100644 index 137bb362b3..0000000000 --- a/AsyncDisplayKitTests/ASPagerNodeTests.m +++ /dev/null @@ -1,97 +0,0 @@ -// -// ASPagerNodeTests.m -// AsyncDisplayKit -// -// Created by Luke Parham on 11/6/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import -#import "ASPagerNode.h" -#import "ASCellNode.h" - -@interface ASPagerNodeTestDataSource : NSObject -@end - -@implementation ASPagerNodeTestDataSource - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - return self; -} - -- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode -{ - return 2; -} - -- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index -{ - return [[ASCellNode alloc] init]; -} - -@end - -@interface ASPagerNodeTestController: UIViewController -@property (nonatomic, strong) ASPagerNodeTestDataSource *testDataSource; -@property (nonatomic, strong) ASPagerNode *pagerNode; -@end - -@implementation ASPagerNodeTestController - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - // Populate these immediately so that they're not unexpectedly nil during tests. - self.testDataSource = [[ASPagerNodeTestDataSource alloc] init]; - - self.pagerNode = [[ASPagerNode alloc] init]; - self.pagerNode.dataSource = self.testDataSource; - - [self.view addSubnode:self.pagerNode]; - } - return self; -} - -@end - -@interface ASPagerNodeTests : XCTestCase -@property (nonatomic, strong) ASPagerNode *pagerNode; - -@property (nonatomic, strong) ASPagerNodeTestDataSource *testDataSource; -@end - -@implementation ASPagerNodeTests - -- (void)testPagerReturnsIndexOfPages { - ASPagerNodeTestController *testController = [self testController]; - - ASCellNode *cellNode = [testController.pagerNode nodeForPageAtIndex:0]; - - XCTAssertEqual([testController.pagerNode indexOfPageWithNode:cellNode], 0); -} - -- (void)testPagerReturnsNotFoundForCellThatDontExistInPager { - ASPagerNodeTestController *testController = [self testController]; - - ASCellNode *badNode = [[ASCellNode alloc] init]; - - XCTAssertEqual([testController.pagerNode indexOfPageWithNode:badNode], NSNotFound); -} - -- (ASPagerNodeTestController *)testController { - ASPagerNodeTestController *testController = [[ASPagerNodeTestController alloc] initWithNibName:nil bundle:nil]; - UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - [window makeKeyAndVisible]; - window.rootViewController = testController; - - [testController.pagerNode reloadData]; - [testController.pagerNode setNeedsLayout]; - - return testController; -} - -@end diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png deleted file mode 100644 index 69e7392384..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png deleted file mode 100644 index 28036afad5..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png deleted file mode 100644 index 692c2a4e84..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png deleted file mode 100644 index 7011b35abf..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png deleted file mode 100644 index b14c267b42..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@2x.png deleted file mode 100644 index 270b15feb6..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@2x.png deleted file mode 100644 index 7fddbff94e..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions@2x.png deleted file mode 100644 index 50cb613c24..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions@2x.png deleted file mode 100644 index 50cb613c24..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png deleted file mode 100644 index 02717f8fdb..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@2x.png deleted file mode 100644 index 311ef9ed32..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png deleted file mode 100644 index 385fc3e817..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@2x.png deleted file mode 100644 index 04ee2d6115..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@2x.png deleted file mode 100644 index 3d9d16b5d2..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png deleted file mode 100644 index d5c6cdfcb3..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png and /dev/null differ diff --git a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png b/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png deleted file mode 100644 index 4f8364937f..0000000000 Binary files a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png and /dev/null differ diff --git a/BUCK b/BUCK new file mode 100755 index 0000000000..2527c2f053 --- /dev/null +++ b/BUCK @@ -0,0 +1,192 @@ +##################################### +# Defines +##################################### +COMMON_PREPROCESSOR_FLAGS = [ + '-fobjc-arc', + '-DDEBUG=1', +] + +COMMON_LANG_PREPROCESSOR_FLAGS = { + 'C': ['-std=gnu99'], + 'CXX': ['-std=c++11', '-stdlib=libc++'], + 'OBJCXX': ['-std=c++11', '-stdlib=libc++'], +} + +COMMON_LINKER_FLAGS = ['-ObjC++'] + +ASYNCDISPLAYKIT_EXPORTED_HEADERS = glob([ + 'Source/*.h', + 'Source/Details/**/*.h', + 'Source/Layout/*.h', + 'Source/Base/*.h', + 'Source/Debug/AsyncDisplayKit+Debug.h', + # 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 + 'Source/TextKit/ASTextNodeTypes.h', + 'Source/TextKit/ASTextKitComponents.h' +]) + +ASYNCDISPLAYKIT_PRIVATE_HEADERS = glob([ + 'Source/**/*.h' + ], + excludes = ASYNCDISPLAYKIT_EXPORTED_HEADERS, +) + +def asyncdisplaykit_library( + name, + additional_preprocessor_flags = [], + deps = [], + additional_frameworks = []): + + apple_library( + name = name, + prefix_header = 'Source/AsyncDisplayKit-Prefix.pch', + header_path_prefix = 'AsyncDisplayKit', + exported_headers = ASYNCDISPLAYKIT_EXPORTED_HEADERS, + headers = ASYNCDISPLAYKIT_PRIVATE_HEADERS, + srcs = glob([ + 'Source/**/*.m', + 'Source/**/*.mm', + 'Source/Base/*.m' + ]), + preprocessor_flags = COMMON_PREPROCESSOR_FLAGS + additional_preprocessor_flags, + lang_preprocessor_flags = COMMON_LANG_PREPROCESSOR_FLAGS, + linker_flags = COMMON_LINKER_FLAGS + [ + '-weak_framework', + 'Photos', + '-weak_framework', + 'MapKit', + ], + deps = deps, + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + '$SDKROOT/System/Library/Frameworks/UIKit.framework', + + '$SDKROOT/System/Library/Frameworks/QuartzCore.framework', + '$SDKROOT/System/Library/Frameworks/CoreMedia.framework', + '$SDKROOT/System/Library/Frameworks/CoreText.framework', + '$SDKROOT/System/Library/Frameworks/CoreGraphics.framework', + '$SDKROOT/System/Library/Frameworks/CoreLocation.framework', + '$SDKROOT/System/Library/Frameworks/AVFoundation.framework', + + # TODO somehow AssetsLibrary can't be weak_framework + '$SDKROOT/System/Library/Frameworks/AssetsLibrary.framework', + ] + additional_frameworks, + visibility = ['PUBLIC'], + ) + +##################################### +# AsyncDisplayKit targets +##################################### +asyncdisplaykit_library( + name = 'AsyncDisplayKit-Core', +) + +# (Default) AsyncDisplayKit and AsyncDisplayKit-PINRemoteImage targets are basically the same library with different names +for name in ['AsyncDisplayKit', 'AsyncDisplayKit-PINRemoteImage']: + asyncdisplaykit_library( + name = name, + deps = [ + '//Pods/PINRemoteImage:PINRemoteImage-PINCache', + ], + additional_frameworks = [ + '$SDKROOT/System/Library/Frameworks/MobileCoreServices.framework', + ] + ) + +##################################### +# Test Host +# TODO: Split to smaller BUCK files and parse in parallel +##################################### +apple_resource( + name = 'TestHostResources', + files = ['Tests/TestHost/Default-568h@2x.png'], + dirs = [], + ) + +apple_bundle( + name = 'TestHost', + binary = ':TestHostBinary', + extension = 'app', + info_plist = 'Tests/TestHost/Info.plist', + info_plist_substitutions = { + 'PRODUCT_BUNDLE_IDENTIFIER': 'com.facebook.AsyncDisplayKitTestHost', + }, + tests = [':Tests'], +) + +apple_binary( + name = 'TestHostBinary', + headers = glob(['Tests/TestHost/*.h']), + srcs = glob(['Tests/TestHost/*.m']), + lang_preprocessor_flags = COMMON_LANG_PREPROCESSOR_FLAGS, + linker_flags = COMMON_LINKER_FLAGS, + deps = [ + ':TestHostResources', + ':AsyncDisplayKit-Core', + ], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Photos.framework', + '$SDKROOT/System/Library/Frameworks/MapKit.framework', + ], +) + +apple_package( + name = 'TestHostPackage', + bundle = ':TestHost', +) + +##################################### +# Tests +##################################### +apple_resource( + name = 'TestsResources', + files = ['Tests/en.lproj/InfoPlist.strings'], + dirs = ['Tests/TestResources'], +) + +apple_test( + name = 'Tests', + test_host_app = ':TestHost', + info_plist = 'Tests/AsyncDisplayKitTests-Info.plist', + info_plist_substitutions = { + 'PRODUCT_BUNDLE_IDENTIFIER': 'com.facebook.AsyncDisplayKitTests', + }, + prefix_header = 'Tests/AsyncDisplayKitTests-Prefix.pch', + header_path_prefix = 'AsyncDisplayKit', + # Expose all ASDK headers to tests + headers = ASYNCDISPLAYKIT_EXPORTED_HEADERS + ASYNCDISPLAYKIT_PRIVATE_HEADERS + glob(['Tests/*.h']), + srcs = glob([ + 'Tests/*.m', + 'Tests/*.mm' + ], + # ASTextNodePerformanceTests are excluded (#2173) + excludes = ['Tests/ASTextNodePerformanceTests.m*'] + ), + snapshot_reference_images_path='Tests/ReferenceImages', + preprocessor_flags = COMMON_PREPROCESSOR_FLAGS + [ + '-Wno-implicit-function-declaration', + '-Wno-deprecated-declarations', + ], + lang_preprocessor_flags = COMMON_LANG_PREPROCESSOR_FLAGS, + linker_flags = COMMON_LINKER_FLAGS, + deps = [ + ':TestsResources', + '//Pods/OCMock:OCMock', + '//Pods/FBSnapshotTestCase:FBSnapshotTestCase', + '//Pods/JGMethodSwizzler:JGMethodSwizzler', + ], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + '$SDKROOT/System/Library/Frameworks/UIKit.framework', + + '$SDKROOT/System/Library/Frameworks/CoreMedia.framework', + '$SDKROOT/System/Library/Frameworks/CoreText.framework', + '$SDKROOT/System/Library/Frameworks/CoreGraphics.framework', + '$SDKROOT/System/Library/Frameworks/AVFoundation.framework', + + '$PLATFORM_DIR/Developer/Library/Frameworks/XCTest.framework', + ], +) diff --git a/Base/ASAssert.h b/Base/ASAssert.h deleted file mode 100644 index 2df66b37a7..0000000000 --- a/Base/ASAssert.h +++ /dev/null @@ -1,59 +0,0 @@ -// -// ASAssert.h -// AsyncDisplayKit -// -// 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. -// - -#pragma once - -#import -#import - -#define ASDisplayNodeAssertWithSignalAndLogFunction(condition, description, logFunction, ...) NSAssert(condition, description, ##__VA_ARGS__); -#define ASDisplayNodeCAssertWithSignalAndLogFunction(condition, description, logFunction, ...) NSCAssert(condition, description, ##__VA_ARGS__); -#define ASDisplayNodeAssertWithSignal(condition, description, ...) NSAssert(condition, description, ##__VA_ARGS__) -#define ASDisplayNodeCAssertWithSignal(condition, description, ...) NSCAssert(condition, description, ##__VA_ARGS__) - -#define ASDISPLAYNODE_ASSERTIONS_ENABLED (!defined(NS_BLOCK_ASSERTIONS)) - -#define ASDisplayNodeAssert(...) NSAssert(__VA_ARGS__) -#define ASDisplayNodeCAssert(...) NSCAssert(__VA_ARGS__) - -#define ASDisplayNodeAssertNil(condition, description, ...) ASDisplayNodeAssertWithSignal(!(condition), nil, (description), ##__VA_ARGS__) -#define ASDisplayNodeCAssertNil(condition, description, ...) ASDisplayNodeCAssertWithSignal(!(condition), nil, (description), ##__VA_ARGS__) - -#define ASDisplayNodeAssertNotNil(condition, description, ...) ASDisplayNodeAssertWithSignal((condition), nil, (description), ##__VA_ARGS__) -#define ASDisplayNodeCAssertNotNil(condition, description, ...) ASDisplayNodeCAssertWithSignal((condition), nil, (description), ##__VA_ARGS__) - -#define ASDisplayNodeAssertImplementedBySubclass() ASDisplayNodeAssertWithSignal(NO, nil, @"This method must be implemented by subclass %@", [self class]); -#define ASDisplayNodeAssertNotInstantiable() ASDisplayNodeAssertWithSignal(NO, nil, @"This class is not instantiable."); -#define ASDisplayNodeAssertNotSupported() ASDisplayNodeAssertWithSignal(NO, nil, @"This method is not supported by class %@", [self class]); - -#define ASDisplayNodeAssertMainThread() ASDisplayNodeAssertWithSignal(0 != pthread_main_np(), nil, @"This method must be called on the main thread") -#define ASDisplayNodeCAssertMainThread() ASDisplayNodeCAssertWithSignal(0 != pthread_main_np(), nil, @"This function must be called on the main thread") - -#define ASDisplayNodeAssertNotMainThread() ASDisplayNodeAssertWithSignal(0 == pthread_main_np(), nil, @"This method must be called off the main thread") -#define ASDisplayNodeCAssertNotMainThread() ASDisplayNodeCAssertWithSignal(0 == pthread_main_np(), nil, @"This function must be called off the main thread") - -#define ASDisplayNodeAssertFlag(X) ASDisplayNodeAssertWithSignal((1 == __builtin_popcount(X)), nil, nil) -#define ASDisplayNodeCAssertFlag(X) ASDisplayNodeCAssertWithSignal((1 == __builtin_popcount(X)), nil, nil) - -#define ASDisplayNodeAssertTrue(condition) ASDisplayNodeAssertWithSignal((condition), nil, nil) -#define ASDisplayNodeCAssertTrue(condition) ASDisplayNodeCAssertWithSignal((condition), nil, nil) - -#define ASDisplayNodeAssertFalse(condition) ASDisplayNodeAssertWithSignal(!(condition), nil, nil) -#define ASDisplayNodeCAssertFalse(condition) ASDisplayNodeCAssertWithSignal(!(condition), nil, nil) - -#define ASDisplayNodeFailAssert(description, ...) ASDisplayNodeAssertWithSignal(NO, (description), ##__VA_ARGS__) -#define ASDisplayNodeCFailAssert(description, ...) ASDisplayNodeCAssertWithSignal(NO, (description), ##__VA_ARGS__) - -#define ASDisplayNodeConditionalAssert(shouldTestCondition, condition, description, ...) ASDisplayNodeAssert((!(shouldTestCondition) || (condition)), nil, (description), ##__VA_ARGS__) -#define ASDisplayNodeConditionalCAssert(shouldTestCondition, condition, description, ...) ASDisplayNodeCAssert((!(shouldTestCondition) || (condition)), nil, (description), ##__VA_ARGS__) - -#define ASDisplayNodeCAssertPositiveReal(description, num) ASDisplayNodeCAssert(num >= 0 && num <= CGFLOAT_MAX, @"%@ must be a real positive integer.", description) -#define ASDisplayNodeCAssertInfOrPositiveReal(description, num) ASDisplayNodeCAssert(isinf(num) || (num >= 0 && num <= CGFLOAT_MAX), @"%@ must be infinite or a real positive integer.", description) - diff --git a/Base/ASAvailability.h b/Base/ASAvailability.h deleted file mode 100644 index 5b30d1963f..0000000000 --- a/Base/ASAvailability.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// ASAvailability.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import -#import - -#import - -#ifndef kCFCoreFoundationVersionNumber_IOS_7_0 -#define kCFCoreFoundationVersionNumber_IOS_7_0 838.00 -#endif - -#ifndef kCFCoreFoundationVersionNumber_iOS_7_1 -#define kCFCoreFoundationVersionNumber_iOS_7_1 847.24 -#endif - -#ifndef kCFCoreFoundationVersionNumber_iOS_8_0 -#define kCFCoreFoundationVersionNumber_iOS_8_0 1140.1 -#endif - -#ifndef kCFCoreFoundationVersionNumber_iOS_8_4 -#define kCFCoreFoundationVersionNumber_iOS_8_4 1145.15 -#endif - -#ifndef kCFCoreFoundationVersionNumber_iOS_9_0 -#define kCFCoreFoundationVersionNumber_iOS_9_0 1240.10 -#endif - -#ifndef kCFCoreFoundationVersionNumber_iOS_10_0 -#define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00 -#endif - -#ifndef __IPHONE_7_0 -#define __IPHONE_7_0 70000 -#endif - -#ifndef __IPHONE_8_0 -#define __IPHONE_8_0 80000 -#endif - -#ifndef __IPHONE_9_0 -#define __IPHONE_9_0 90000 -#endif - -#ifndef __IPHONE_10_0 -#define __IPHONE_10_0 100000 -#endif - -#ifndef AS_IOS8_SDK_OR_LATER -#define AS_IOS8_SDK_OR_LATER __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0 -#endif - -#define AS_AT_LEAST_IOS7 (kCFCoreFoundationVersionNumber > kCFCoreFoundationVersionNumber_iOS_6_1) -#define AS_AT_LEAST_IOS7_1 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_1) -#define AS_AT_LEAST_IOS8 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) -#define AS_AT_LEAST_IOS9 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0) -#define AS_AT_LEAST_IOS10 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2861dc49e5..e1df6d05e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,10 +61,6 @@ Complete your CLA here: We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. -Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe -disclosure of security bugs. In those cases, please go through the process -outlined on that page and do not file a public issue. - ## Getting Help We use Slack for real-time debugging, community updates, and general talk about ASDK. Signup at http://asdk-slack-auto-invite.herokuapp.com or email AsyncDisplayKit(at)gmail.com to get an invite. diff --git a/Podfile b/Podfile index d1b954c71b..4e3fc26b2a 100644 --- a/Podfile +++ b/Podfile @@ -1,9 +1,31 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target :'AsyncDisplayKitTests' do pod 'OCMock', '~> 2.2' pod 'FBSnapshotTestCase/Core', '~> 2.1' pod 'JGMethodSwizzler', :git => 'https://github.com/JonasGessner/JGMethodSwizzler', :branch => 'master' + + # Only for buck build + pod 'PINRemoteImage', '3.0.0-beta.7' + + #TODO CocoaPods plugin instead? + post_install do |installer| + require 'fileutils' + + # Assuming we're at the root dir + buck_files_dir = 'buck-files' + if File.directory?(buck_files_dir) + installer.pod_targets.flat_map do |pod_target| + pod_name = pod_target.pod_name + # Copy the file at buck-files/BUCK_pod_name to Pods/pod_name/BUCK, + # override existing file if needed + buck_file = buck_files_dir + '/BUCK_' + pod_name + if File.file?(buck_file) + FileUtils.cp(buck_file, 'Pods/' + pod_name + '/BUCK', :preserve => false) + end + end + end + end end diff --git a/README.md b/README.md index 6631041e76..01b0a51945 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,58 @@ -![AsyncDisplayKit](https://github.com/facebook/AsyncDisplayKit/blob/gh-pages/static/images/logo.png) - -[![Apps Using](https://img.shields.io/badge/Apps%20Using%20ASDK-%3E4,974-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit) -[![Downloads](https://img.shields.io/badge/Total%20Downloads-%3E512,000-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit) +

AsyncDisplayKit has been moved and renamed: Texture

+Texture Logo +

Learn more here

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +![AsyncDisplayKit](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/logo.png) + +[![Apps Using](https://img.shields.io/cocoapods/at/AsyncDisplayKit.svg?label=Apps%20Using%20ASDK&colorB=28B9FE)](http://cocoapods.org/pods/AsyncDisplayKit) +[![Downloads](https://img.shields.io/cocoapods/dt/AsyncDisplayKit.svg?label=Total%20Downloads&colorB=28B9FE)](http://cocoapods.org/pods/AsyncDisplayKit) [![Platform](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS-orange.svg)](http://AsyncDisplayKit.org) [![Languages](https://img.shields.io/badge/languages-ObjC%20%7C%20Swift-orange.svg)](http://AsyncDisplayKit.org) [![Version](https://img.shields.io/cocoapods/v/AsyncDisplayKit.svg)](http://cocoapods.org/pods/AsyncDisplayKit) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-59C939.svg?style=flat)](https://github.com/Carthage/Carthage) -[![Build Status](https://travis-ci.org/facebook/AsyncDisplayKit.svg)](https://travis-ci.org/facebook/AsyncDisplayKit) [![License](https://img.shields.io/cocoapods/l/AsyncDisplayKit.svg)](https://github.com/facebook/AsyncDisplayKit/blob/master/LICENSE) - + ## Installation -ASDK is available via CocoaPods or Carthage. See our [Installation](http://asyncdisplaykit.org/docs/installation.html) guide for instructions. +ASDK is available via CocoaPods or Carthage. See our [Installation](http://asyncdisplaykit.org/docs/installation.html) guide for instructions. ## Performance Gains @@ -21,7 +60,7 @@ AsyncDisplayKit's basic unit is the `node`. An ASDisplayNode is an abstraction o To keep its user interface smooth and responsive, your app should render at 60 frames per second — the gold standard on iOS. This means the main thread has one-sixtieth of a second to push each frame. That's 16 milliseconds to execute all layout and drawing code! And because of system overhead, your code usually has less than ten milliseconds to run before it causes a frame drop. -AsyncDisplayKit lets you move image decoding, text sizing and rendering, layout, and other expensive UI operations off the main thread, to keep the main thread available to respond to user interaction. +AsyncDisplayKit lets you move image decoding, text sizing and rendering, layout, and other expensive UI operations off the main thread, to keep the main thread available to respond to user interaction. ## Advanced Developer Features @@ -35,7 +74,7 @@ As the framework has grown, many features have been added that can save develope ## Getting Help -We use Slack for real-time debugging, community updates, and general talk about ASDK. [Signup](http://asdk-slack-auto-invite.herokuapp.com) youself or email AsyncDisplayKit(at)gmail.com to get an invite. +We use Slack for real-time debugging, community updates, and general talk about ASDK. [Signup](http://asdk-slack-auto-invite.herokuapp.com) yourself or email AsyncDisplayKit(at)gmail.com to get an invite. ## Contributing diff --git a/Source/ASBlockTypes.h b/Source/ASBlockTypes.h new file mode 100644 index 0000000000..c6aaefdcba --- /dev/null +++ b/Source/ASBlockTypes.h @@ -0,0 +1,19 @@ +// +// ASBlockTypes.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/25/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +@class ASCellNode; + +/** + * ASCellNode creation block. Used to lazily create the ASCellNode instance for a specified indexPath. + */ +typedef ASCellNode * _Nonnull(^ASCellNodeBlock)(); + +// Type for the cancellation checker block passed into the async display blocks. YES means the operation has been cancelled, NO means continue. +typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); diff --git a/AsyncDisplayKit/ASButtonNode.h b/Source/ASButtonNode.h similarity index 72% rename from AsyncDisplayKit/ASButtonNode.h rename to Source/ASButtonNode.h index 15b542eb64..2f33173819 100644 --- a/AsyncDisplayKit/ASButtonNode.h +++ b/Source/ASButtonNode.h @@ -8,11 +8,13 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import -#import +#import +#import NS_ASSUME_NONNULL_BEGIN +@class ASImageNode, ASTextNode; + /** Image alignment defines where the image will be placed relative to the text. */ @@ -64,19 +66,19 @@ typedef NS_ENUM(NSInteger, ASButtonNodeImageAlignment) { /** * Returns the styled title associated with the specified state. * - * @param state The state that uses the styled title. The possible values are described in ASControlState. + * @param state The control state that uses the styled title. * * @return The title for the specified state. */ -- (NSAttributedString * _Nullable)attributedTitleForState:(ASControlState)state AS_WARN_UNUSED_RESULT; +- (nullable NSAttributedString *)attributedTitleForState:(UIControlState)state AS_WARN_UNUSED_RESULT; /** * Sets the styled title to use for the specified state. This will reset styled title previously set with -setTitle:withFont:withColor:forState. * * @param title The styled text string to use for the title. - * @param state The state that uses the specified title. The possible values are described in ASControlState. + * @param state The control state that uses the specified title. */ -- (void)setAttributedTitle:(nullable NSAttributedString *)title forState:(ASControlState)state; +- (void)setAttributedTitle:(nullable NSAttributedString *)title forState:(UIControlState)state; #if TARGET_OS_IOS /** @@ -85,44 +87,44 @@ typedef NS_ENUM(NSInteger, ASButtonNodeImageAlignment) { * @param title The styled text string to use for the title. * @param font The font to use for the title. * @param color The color to use for the title. - * @param state The state that uses the specified title. The possible values are described in ASControlState. + * @param state The control state that uses the specified title. */ -- (void)setTitle:(NSString *)title withFont:(nullable UIFont *)font withColor:(nullable UIColor *)color forState:(ASControlState)state; +- (void)setTitle:(NSString *)title withFont:(nullable UIFont *)font withColor:(nullable UIColor *)color forState:(UIControlState)state; #endif /** * Returns the image used for a button state. * - * @param state The state that uses the image. Possible values are described in ASControlState. + * @param state The control state that uses the image. * * @return The image used for the specified state. */ -- (nullable UIImage *)imageForState:(ASControlState)state AS_WARN_UNUSED_RESULT; +- (nullable UIImage *)imageForState:(UIControlState)state AS_WARN_UNUSED_RESULT; /** * Sets the image to use for the specified state. * * @param image The image to use for the specified state. - * @param state The state that uses the specified title. The values are described in ASControlState. + * @param state The control state that uses the specified title. */ -- (void)setImage:(nullable UIImage *)image forState:(ASControlState)state; +- (void)setImage:(nullable UIImage *)image forState:(UIControlState)state; /** * Sets the background image to use for the specified state. * * @param image The image to use for the specified state. - * @param state The state that uses the specified title. The values are described in ASControlState. + * @param state The control state that uses the specified title. */ -- (void)setBackgroundImage:(nullable UIImage *)image forState:(ASControlState)state; +- (void)setBackgroundImage:(nullable UIImage *)image forState:(UIControlState)state; /** * Returns the background image used for a button state. * - * @param state The state that uses the image. Possible values are described in ASControlState. + * @param state The control state that uses the image. * * @return The background image used for the specified state. */ -- (nullable UIImage *)backgroundImageForState:(ASControlState)state AS_WARN_UNUSED_RESULT; +- (nullable UIImage *)backgroundImageForState:(UIControlState)state AS_WARN_UNUSED_RESULT; @end diff --git a/AsyncDisplayKit/ASButtonNode.mm b/Source/ASButtonNode.mm similarity index 71% rename from AsyncDisplayKit/ASButtonNode.mm rename to Source/ASButtonNode.mm index f260fd6a45..a092549cea 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/Source/ASButtonNode.mm @@ -8,18 +8,18 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASButtonNode.h" -#import "ASStackLayoutSpec.h" -#import "ASThread.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASBackgroundLayoutSpec.h" -#import "ASInsetLayoutSpec.h" -#import "ASAbsoluteLayoutSpec.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import @interface ASButtonNode () { - ASDN::RecursiveMutex __instanceLock__; - NSAttributedString *_normalAttributedTitle; NSAttributedString *_highlightedAttributedTitle; NSAttributedString *_selectedAttributedTitle; @@ -154,7 +154,7 @@ - (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously - (void)updateImage { - ASDN::MutexLocker l(__instanceLock__); + __instanceLock__.lock(); UIImage *newImage; if (self.enabled == NO && _disabledImage) { @@ -171,13 +171,19 @@ - (void)updateImage if ((_imageNode != nil || newImage != nil) && newImage != self.imageNode.image) { _imageNode.image = newImage; + __instanceLock__.unlock(); + [self setNeedsLayout]; + return; } + + __instanceLock__.unlock(); } - (void)updateTitle { - ASDN::MutexLocker l(__instanceLock__); + __instanceLock__.lock(); + NSAttributedString *newTitle; if (self.enabled == NO && _disabledAttributedTitle) { newTitle = _disabledAttributedTitle; @@ -191,16 +197,22 @@ - (void)updateTitle newTitle = _normalAttributedTitle; } + // Calling self.titleNode is essential here because _titleNode is lazily created by the getter. if ((_titleNode != nil || newTitle.length > 0) && [self.titleNode.attributedText isEqualToAttributedString:newTitle] == NO) { _titleNode.attributedText = newTitle; + __instanceLock__.unlock(); + self.accessibilityLabel = _titleNode.accessibilityLabel; [self setNeedsLayout]; + return; } + + __instanceLock__.unlock(); } - (void)updateBackgroundImage { - ASDN::MutexLocker l(__instanceLock__); + __instanceLock__.lock(); UIImage *newImage; if (self.enabled == NO && _disabledBackgroundImage) { @@ -217,8 +229,13 @@ - (void)updateBackgroundImage if ((_backgroundImageNode != nil || newImage != nil) && newImage != self.backgroundImageNode.image) { _backgroundImageNode.image = newImage; + __instanceLock__.unlock(); + [self setNeedsLayout]; + return; } + + __instanceLock__.unlock(); } - (CGFloat)contentSpacing @@ -229,11 +246,15 @@ - (CGFloat)contentSpacing - (void)setContentSpacing:(CGFloat)contentSpacing { - ASDN::MutexLocker l(__instanceLock__); - if (contentSpacing == _contentSpacing) - return; - - _contentSpacing = contentSpacing; + { + ASDN::MutexLocker l(__instanceLock__); + if (contentSpacing == _contentSpacing) { + return; + } + + _contentSpacing = contentSpacing; + } + [self setNeedsLayout]; } @@ -245,11 +266,15 @@ - (BOOL)laysOutHorizontally - (void)setLaysOutHorizontally:(BOOL)laysOutHorizontally { - ASDN::MutexLocker l(__instanceLock__); - if (laysOutHorizontally == _laysOutHorizontally) - return; + { + ASDN::MutexLocker l(__instanceLock__); + if (laysOutHorizontally == _laysOutHorizontally) { + return; + } - _laysOutHorizontally = laysOutHorizontally; + _laysOutHorizontally = laysOutHorizontally; + } + [self setNeedsLayout]; } @@ -303,36 +328,35 @@ - (void)setImageAlignment:(ASButtonNodeImageAlignment)imageAlignment #if TARGET_OS_IOS -- (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(ASControlState)state +- (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(UIControlState)state { NSDictionary *attributes = @{ - NSFontAttributeName: font ? : [UIFont systemFontOfSize:[UIFont buttonFontSize]], - NSForegroundColorAttributeName : color ? : [UIColor blackColor] - }; + NSFontAttributeName: font ? : [UIFont systemFontOfSize:[UIFont buttonFontSize]], + NSForegroundColorAttributeName : color ? : [UIColor blackColor] + }; - NSAttributedString *string = [[NSAttributedString alloc] initWithString:title - attributes:attributes]; + NSAttributedString *string = [[NSAttributedString alloc] initWithString:title attributes:attributes]; [self setAttributedTitle:string forState:state]; } #endif -- (NSAttributedString *)attributedTitleForState:(ASControlState)state +- (NSAttributedString *)attributedTitleForState:(UIControlState)state { ASDN::MutexLocker l(__instanceLock__); switch (state) { - case ASControlStateNormal: + case UIControlStateNormal: return _normalAttributedTitle; - case ASControlStateHighlighted: + case UIControlStateHighlighted: return _highlightedAttributedTitle; - case ASControlStateSelected: + case UIControlStateSelected: return _selectedAttributedTitle; - case ASControlStateSelected | ASControlStateHighlighted: + case UIControlStateSelected | UIControlStateHighlighted: return _selectedHighlightedAttributedTitle; - case ASControlStateDisabled: + case UIControlStateDisabled: return _disabledAttributedTitle; default: @@ -340,54 +364,56 @@ - (NSAttributedString *)attributedTitleForState:(ASControlState)state } } -- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASControlState)state +- (void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState)state { - ASDN::MutexLocker l(__instanceLock__); - switch (state) { - case ASControlStateNormal: - _normalAttributedTitle = [title copy]; - break; - - case ASControlStateHighlighted: - _highlightedAttributedTitle = [title copy]; - break; - - case ASControlStateSelected: - _selectedAttributedTitle = [title copy]; - break; - - case ASControlStateSelected | ASControlStateHighlighted: - _selectedHighlightedAttributedTitle = [title copy]; - break; - - case ASControlStateDisabled: - _disabledAttributedTitle = [title copy]; - break; - - default: - break; + { + ASDN::MutexLocker l(__instanceLock__); + switch (state) { + case UIControlStateNormal: + _normalAttributedTitle = [title copy]; + break; + + case UIControlStateHighlighted: + _highlightedAttributedTitle = [title copy]; + break; + + case UIControlStateSelected: + _selectedAttributedTitle = [title copy]; + break; + + case UIControlStateSelected | UIControlStateHighlighted: + _selectedHighlightedAttributedTitle = [title copy]; + break; + + case UIControlStateDisabled: + _disabledAttributedTitle = [title copy]; + break; + + default: + break; + } } [self updateTitle]; } -- (UIImage *)imageForState:(ASControlState)state +- (UIImage *)imageForState:(UIControlState)state { ASDN::MutexLocker l(__instanceLock__); switch (state) { - case ASControlStateNormal: + case UIControlStateNormal: return _normalImage; - case ASControlStateHighlighted: + case UIControlStateHighlighted: return _highlightedImage; - case ASControlStateSelected: + case UIControlStateSelected: return _selectedImage; - case ASControlStateSelected | ASControlStateHighlighted: + case UIControlStateSelected | UIControlStateHighlighted: return _selectedHighlightedImage; - case ASControlStateDisabled: + case UIControlStateDisabled: return _disabledImage; default: @@ -395,53 +421,56 @@ - (UIImage *)imageForState:(ASControlState)state } } -- (void)setImage:(UIImage *)image forState:(ASControlState)state +- (void)setImage:(UIImage *)image forState:(UIControlState)state { - ASDN::MutexLocker l(__instanceLock__); - switch (state) { - case ASControlStateNormal: - _normalImage = image; - break; - - case ASControlStateHighlighted: - _highlightedImage = image; - break; - - case ASControlStateSelected: - _selectedImage = image; - break; - - case ASControlStateSelected | ASControlStateHighlighted: - _selectedHighlightedImage = image; - break; - - case ASControlStateDisabled: - _disabledImage = image; - break; + { + ASDN::MutexLocker l(__instanceLock__); + switch (state) { + case UIControlStateNormal: + _normalImage = image; + break; + + case UIControlStateHighlighted: + _highlightedImage = image; + break; + + case UIControlStateSelected: + _selectedImage = image; + break; - default: - break; + case UIControlStateSelected | UIControlStateHighlighted: + _selectedHighlightedImage = image; + break; + + case UIControlStateDisabled: + _disabledImage = image; + break; + + default: + break; + } } + [self updateImage]; } -- (UIImage *)backgroundImageForState:(ASControlState)state +- (UIImage *)backgroundImageForState:(UIControlState)state { ASDN::MutexLocker l(__instanceLock__); switch (state) { - case ASControlStateNormal: + case UIControlStateNormal: return _normalBackgroundImage; - case ASControlStateHighlighted: + case UIControlStateHighlighted: return _highlightedBackgroundImage; - case ASControlStateSelected: + case UIControlStateSelected: return _selectedBackgroundImage; - case ASControlStateSelected | ASControlStateHighlighted: + case UIControlStateSelected | UIControlStateHighlighted: return _selectedHighlightedBackgroundImage; - case ASControlStateDisabled: + case UIControlStateDisabled: return _disabledBackgroundImage; default: @@ -449,33 +478,36 @@ - (UIImage *)backgroundImageForState:(ASControlState)state } } -- (void)setBackgroundImage:(UIImage *)image forState:(ASControlState)state +- (void)setBackgroundImage:(UIImage *)image forState:(UIControlState)state { - ASDN::MutexLocker l(__instanceLock__); - switch (state) { - case ASControlStateNormal: - _normalBackgroundImage = image; - break; - - case ASControlStateHighlighted: - _highlightedBackgroundImage = image; - break; - - case ASControlStateSelected: - _selectedBackgroundImage = image; - break; - - case ASControlStateSelected | ASControlStateHighlighted: - _selectedHighlightedBackgroundImage = image; - break; - - case ASControlStateDisabled: - _disabledBackgroundImage = image; - break; - - default: - break; + { + ASDN::MutexLocker l(__instanceLock__); + switch (state) { + case UIControlStateNormal: + _normalBackgroundImage = image; + break; + + case UIControlStateHighlighted: + _highlightedBackgroundImage = image; + break; + + case UIControlStateSelected: + _selectedBackgroundImage = image; + break; + + case UIControlStateSelected | UIControlStateHighlighted: + _selectedHighlightedBackgroundImage = image; + break; + + case UIControlStateDisabled: + _disabledBackgroundImage = image; + break; + + default: + break; + } } + [self updateBackgroundImage]; } diff --git a/AsyncDisplayKit/ASCellNode.h b/Source/ASCellNode.h similarity index 87% rename from AsyncDisplayKit/ASCellNode.h rename to Source/ASCellNode.h index d5ae55aec4..4957afdffb 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/Source/ASCellNode.h @@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN -@class ASCellNode; +@class ASCellNode, ASTextNode; typedef NSUInteger ASCellNodeAnimation; @@ -79,6 +79,7 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { * * @return The supplementary element kind, or @c nil if this node does not represent a supplementary element. */ +//TODO change this to be a generic "kind" or "elementKind" that exposes `nil` for row kind @property (nonatomic, copy, readonly, nullable) NSString *supplementaryElementKind; /* @@ -90,12 +91,6 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { */ @property (nonatomic, strong, readonly, nullable) UICollectionViewLayoutAttributes *layoutAttributes; -/* - * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. - */ -//@property (nonatomic, retain) UIColor *backgroundColor; -@property (nonatomic) UITableViewCellSelectionStyle selectionStyle; - /** * A Boolean value that is synchronized with the underlying collection or tableView cell property. * Setting this value is equivalent to calling selectItem / deselectItem on the collection or table. @@ -167,6 +162,31 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { */ - (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(nullable UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; +#pragma mark - UITableViewCell specific passthrough properties + +/* @abstract The selection style when a tap on a cell occurs + * @default UITableViewCellSelectionStyleDefault + * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. + */ +@property (nonatomic) UITableViewCellSelectionStyle selectionStyle; + +/* @abstract The view used as the background of the cell when it is selected. + * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. + * ASCollectionView uses these properties when configuring UICollectionViewCells that host ASCellNodes. + */ +@property (nonatomic, strong, nullable) UIView *selectedBackgroundView; + +/* @abstract The accessory type view on the right side of the cell. Please take care of your ASLayoutSpec so that doesn't overlay the accessoryView + * @default UITableViewCellAccessoryNone + * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. + */ +@property (nonatomic) UITableViewCellAccessoryType accessoryType; + +/* @abstract The inset of the cell separator line + * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. + */ +@property (nonatomic) UIEdgeInsets separatorInset; + @end @interface ASCellNode (Unavailable) @@ -205,6 +225,11 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { */ @property (nonatomic, assign) UIEdgeInsets textInsets; +/** + * The text node used by this cell node. + */ +@property (nonatomic, strong, readonly) ASTextNode *textNode; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASCellNode.mm b/Source/ASCellNode.mm similarity index 81% rename from AsyncDisplayKit/ASCellNode.mm rename to Source/ASCellNode.mm index 7ddd50cab7..f98b1cb994 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/Source/ASCellNode.mm @@ -8,14 +8,14 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASCellNode+Internal.h" - -#import "ASEqualityHelpers.h" -#import "ASInternalHelpers.h" -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASCollectionView+Undeprecated.h" -#import "ASTableView+Undeprecated.h" +#import + +#import +#import +#import +#import +#import +#import #import #import #import @@ -25,6 +25,7 @@ #import #import +#import #pragma mark - #pragma mark ASCellNode @@ -48,7 +49,6 @@ @interface ASCellNode () @implementation ASCellNode @synthesize interactionDelegate = _interactionDelegate; -static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +initialize. - (instancetype)init { @@ -88,8 +88,10 @@ - (void)didLoad _viewControllerNode = asViewController.node; [_viewController view]; } else { + // Careful to avoid retain cycle + UIViewController *viewController = _viewController; _viewControllerNode = [[ASDisplayNode alloc] initWithViewBlock:^{ - return _viewController.view; + return viewController.view; }]; } [self addSubnode:_viewControllerNode]; @@ -118,56 +120,21 @@ - (void)layoutDidFinish _viewControllerNode.frame = self.bounds; } -- (void)_locked_displayNodeDidInvalidateSizeNewSize:(CGSize)newSize +- (void)_rootNodeDidInvalidateSize { - CGSize oldSize = self.bounds.size; - if (CGSizeEqualToSize(oldSize, newSize) == NO) { - self.frame = {self.frame.origin, newSize}; - [self didRelayoutFromOldSize:oldSize toNewSize:newSize]; + if (_interactionDelegate != nil) { + [_interactionDelegate nodeDidInvalidateSize:self]; + } else { + [super _rootNodeDidInvalidateSize]; } } -- (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 +- (void)_layoutTransitionMeasurementDidFinish { if (_interactionDelegate != nil) { - ASPerformBlockOnMainThread(^{ - BOOL sizeChanged = !CGSizeEqualToSize(oldSize, newSize); - [_interactionDelegate nodeDidRelayout:self sizeChanged:sizeChanged]; - }); + [_interactionDelegate nodeDidInvalidateSize:self]; + } else { + [super _layoutTransitionMeasurementDidFinish]; } } @@ -315,20 +282,25 @@ - (void)didExitVisibleState [self handleVisibilityChange:NO]; } -+ (void)initialize ++ (BOOL)requestsVisibilityNotifications { - [super initialize]; - if (ASSubclassOverridesSelector([ASCellNode class], self, @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:))) { - if (__cellClassesForVisibilityNotifications == nil) { - __cellClassesForVisibilityNotifications = [NSMutableSet set]; - } - [__cellClassesForVisibilityNotifications addObject:self]; + static NSCache *cache; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + cache = [[NSCache alloc] init]; + }); + NSNumber *result = [cache objectForKey:self]; + if (result == nil) { + BOOL overrides = ASSubclassOverridesSelector([ASCellNode class], self, @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:)); + result = overrides ? (NSNumber *)kCFBooleanTrue : (NSNumber *)kCFBooleanFalse; + [cache setObject:result forKey:self]; } + return (result == (NSNumber *)kCFBooleanTrue); } - (void)handleVisibilityChange:(BOOL)isVisible { - if ([__cellClassesForVisibilityNotifications containsObject:[self class]] == NO) { + if ([self.class requestsVisibilityNotifications] == NO) { return; // The work below is expensive, and only valuable for subclasses watching visibility events. } @@ -339,7 +311,7 @@ - (void)handleVisibilityChange:(BOOL)isVisible CGRect cellFrame = CGRectZero; // Ensure our _scrollView is still valid before converting. It's also possible that we have already been removed from the _scrollView, - // in which case it is not valid to perform a convertRect (this actually crashes on iOS 7 and 8). + // in which case it is not valid to perform a convertRect (this actually crashes on iOS 8). UIScrollView *scrollView = (_scrollView != nil && view.superview != nil && [view isDescendantOfView:_scrollView]) ? _scrollView : nil; if (scrollView) { cellFrame = [view convertRect:view.bounds toView:_scrollView]; @@ -390,19 +362,18 @@ - (void)handleVisibilityChange:(BOOL)isVisible return result; } + +- (NSString *)supplementaryElementKind +{ + return self.collectionElement.supplementaryElementKind; +} + @end #pragma mark - #pragma mark ASTextCellNode -@interface ASTextCellNode () - -@property (nonatomic, strong) ASTextNode *textNode; - -@end - - @implementation ASTextCellNode static const CGFloat kASTextCellNodeDefaultFontSize = 18.0f; @@ -421,7 +392,7 @@ - (instancetype)initWithAttributes:(NSDictionary *)textAttributes insets:(UIEdge _textInsets = textInsets; _textAttributes = [textAttributes copy]; _textNode = [[ASTextNode alloc] init]; - [self addSubnode:_textNode]; + self.automaticallyManagesSubnodes = YES; } return self; } diff --git a/AsyncDisplayKit/ASCollectionNode+Beta.h b/Source/ASCollectionNode+Beta.h similarity index 54% rename from AsyncDisplayKit/ASCollectionNode+Beta.h rename to Source/ASCollectionNode+Beta.h index d1476c0baf..11768b5f63 100644 --- a/AsyncDisplayKit/ASCollectionNode+Beta.h +++ b/Source/ASCollectionNode+Beta.h @@ -8,15 +8,33 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASCollectionNode.h" -@protocol ASCollectionViewLayoutFacilitatorProtocol; +#import + +@protocol ASCollectionViewLayoutFacilitatorProtocol, ASCollectionLayoutDelegate; +@class ASElementMap; NS_ASSUME_NONNULL_BEGIN @interface ASCollectionNode (Beta) +/** + * Allows providing a custom subclass of ASCollectionView to be managed by ASCollectionNode. + * + * @default [ASCollectionView class] is used whenever this property is unset or nil. + */ +@property (strong, nonatomic, nullable) Class collectionViewClass; + +/** + * The elements that are currently displayed. The "UIKit index space". Must be accessed on main thread. + */ +@property (strong, nonatomic, readonly) ASElementMap *visibleElements; + +@property (strong, readonly, nullable) id layoutDelegate; + - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator; +- (instancetype)initWithLayoutDelegate:(id)layoutDelegate layoutFacilitator:(nullable id)layoutFacilitator; + - (void)beginUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead."); - (void)endUpdatesAnimated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead."); diff --git a/AsyncDisplayKit/ASCollectionNode.h b/Source/ASCollectionNode.h similarity index 87% rename from AsyncDisplayKit/ASCollectionNode.h rename to Source/ASCollectionNode.h index ef6e5e9c2a..e8163510cb 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/Source/ASCollectionNode.h @@ -14,6 +14,7 @@ #import #import #import +#import @protocol ASCollectionViewLayoutFacilitatorProtocol; @protocol ASCollectionDelegate; @@ -28,6 +29,8 @@ NS_ASSUME_NONNULL_BEGIN */ @interface ASCollectionNode : ASDisplayNode +- (instancetype)init NS_UNAVAILABLE; + /** * Initializes an ASCollectionNode * @@ -74,6 +77,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property (weak, nonatomic) id dataSource; +/* + * A Boolean value that determines whether the collection node will be flipped. + * If the value of this property is YES, the first cell node will be at the bottom of the collection node (as opposed to the top by default). This is useful for chat/messaging apps. The default value is NO. + */ +@property (nonatomic, assign) BOOL inverted; + /** * A Boolean value that indicates whether users can select items in the collection node. * If the value of this property is YES (the default), users can select items. If you want more fine-grained control over the selection of items, you must provide a delegate object and implement the appropriate methods of the UICollectionNodeDelegate protocol. @@ -87,6 +96,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) BOOL allowsMultipleSelection; +/** + * The layout used to organize the node's items. + * + * @discussion Assigning a new layout object to this property causes the new layout to be applied (without animations) to the node’s items. + */ +@property (nonatomic, strong) UICollectionViewLayout *collectionViewLayout; /** * Tuning parameters for a range type in full mode. @@ -171,7 +186,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. @@ -182,7 +197,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread. @@ -465,7 +480,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Similar to -collectionView:cellForItemAtIndexPath:. * - * @param collectionView The sender. + * @param collectionNode The sender. * @param indexPath The index path of the item. * * @return A node to display for the given item. This will be called on the main thread and should @@ -474,6 +489,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForItemAtIndexPath:(NSIndexPath *)indexPath; +/** + * Asks the data source to provide a node-block to display for the given supplementary element in the collection view. + * + * @param collectionNode The sender. + * @param kind The kind of supplementary element. + * @param indexPath The index path of the supplementary element. + */ +- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + /** * Asks the data source to provide a node to display for the given supplementary element in the collection view. * @@ -495,6 +519,16 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable id)collectionNode:(ASCollectionNode *)collectionNode contextForSection:(NSInteger)section; +/** + * Asks the data source to provide an array of supplementary element kinds that exist in a given section. + * + * @param collectionNode The sender. + * @param section The index of the section to provide supplementary kinds for. + * + * @return The supplementary element kinds that exist in the given section, if any. + */ +- (NSArray *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section; + /** * Similar to -collectionView:cellForItemAtIndexPath:. * @@ -690,4 +724,57 @@ NS_ASSUME_NONNULL_BEGIN @end +@protocol ASCollectionDataSourceInterop + +/** + * This method offers compatibility with synchronous, standard UICollectionViewCell objects. + * These cells will **not** have the performance benefits of ASCellNodes (like preloading, async layout, and + * async drawing) - even when mixed within the same ASCollectionNode. + * + * In order to use this method, you must: + * 1. Implement it on your ASCollectionDataSource object. + * 2. Call registerCellClass: on the collectionNode.view (in viewDidLoad, or register an onDidLoad: block). + * 3. Return nil from the nodeBlockForItem...: or nodeForItem...: method. NOTE: it is an error to return + * nil from within a nodeBlock, if you have returned a nodeBlock object. + * 4. Lastly, you must implement a method to provide the size for the cell. There are two ways this is done: + * 4a. UICollectionViewFlowLayout (incl. ASPagerNode). Implement + collectionNode:constrainedSizeForItemAtIndexPath:. + * 4b. Custom collection layouts. Set .view.layoutInspector and have it implement + collectionView:constrainedSizeForNodeAtIndexPath:. + * + * For an example of using this method with all steps above (including a custom layout, 4b.), + * see the app in examples/CustomCollectionView and enable kShowUICollectionViewCells = YES. + */ +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; + +@optional + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + +/** + * Implement this property and return YES if you want your interop data source to be + * used when dequeuing cells for node-backed items. + * + * If NO (the default), the interop data source will only be consulted in cases + * where no ASCellNode was provided to AsyncDisplayKit. + * + * If YES, the interop data source will always be consulted to dequeue cells, and + * will be expected to return _ASCollectionViewCells in cases where a node was provided. + * + * The default value is NO. + */ +@property (class, nonatomic, readonly) BOOL dequeuesCellsForNodeBackedItems; + +@end + +@protocol ASCollectionDelegateInterop + +@optional + +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath; + +- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath; + +@end + NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/Source/ASCollectionNode.mm similarity index 65% rename from AsyncDisplayKit/ASCollectionNode.mm rename to Source/ASCollectionNode.mm index de39662d16..aa320f495f 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -10,27 +10,36 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASCollectionInternal.h" -#import "ASCollectionViewLayoutFacilitatorProtocol.h" -#import "ASCollectionNode.h" -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASEnvironmentInternal.h" -#import "ASInternalHelpers.h" -#import "ASCellNode+Internal.h" -#import "AsyncDisplayKit+Debug.h" -#import "ASSectionContext.h" -#import "ASCollectionDataController.h" -#import "ASCollectionView+Undeprecated.h" +#import +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import #pragma mark - _ASCollectionPendingState @interface _ASCollectionPendingState : NSObject @property (weak, nonatomic) id delegate; @property (weak, nonatomic) id dataSource; +@property (strong, nonatomic) UICollectionViewLayout *collectionViewLayout; @property (nonatomic, assign) ASLayoutRangeMode rangeMode; @property (nonatomic, assign) BOOL allowsSelection; // default is YES @property (nonatomic, assign) BOOL allowsMultipleSelection; // default is NO +@property (nonatomic, assign) BOOL inverted; //default is NO @end @implementation _ASCollectionPendingState @@ -39,9 +48,10 @@ - (instancetype)init { self = [super init]; if (self) { - _rangeMode = ASLayoutRangeModeCount; + _rangeMode = ASLayoutRangeModeUnspecified; _allowsSelection = YES; _allowsMultipleSelection = NO; + _inverted = NO; } return self; } @@ -58,7 +68,7 @@ - (instancetype)init self = [super init]; if (self) { _tuningParameters = std::vector> (ASLayoutRangeModeCount, std::vector (ASLayoutRangeTypeCount)); - _rangeMode = ASLayoutRangeModeCount; + _rangeMode = ASLayoutRangeModeUnspecified; } return self; } @@ -93,6 +103,7 @@ - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMo @interface ASCollectionNode () { ASDN::RecursiveMutex _environmentStateLock; + Class _collectionViewClass; } @property (nonatomic) _ASCollectionPendingState *pendingState; @end @@ -101,17 +112,23 @@ @implementation ASCollectionNode #pragma mark Lifecycle -- (instancetype)init +- (Class)collectionViewClass { - ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); - UICollectionViewLayout *nilLayout = nil; - self = [self initWithCollectionViewLayout:nilLayout]; // Will throw an exception for lacking a UICV Layout. - return nil; + return _collectionViewClass ? : [ASCollectionView class]; +} + +- (void)setCollectionViewClass:(Class)collectionViewClass +{ + if (_collectionViewClass != collectionViewClass) { + ASDisplayNodeAssert([collectionViewClass isSubclassOfClass:[ASCollectionView class]] || collectionViewClass == Nil, @"ASCollectionNode requires that .collectionViewClass is an ASCollectionView subclass"); + ASDisplayNodeAssert([self isNodeLoaded] == NO, @"ASCollectionNode's .collectionViewClass cannot be changed after the view is loaded"); + _collectionViewClass = collectionViewClass; + } } - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout { - return [self initWithFrame:CGRectZero collectionViewLayout:layout]; + return [self initWithFrame:CGRectZero collectionViewLayout:layout layoutFacilitator:nil]; } - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout @@ -119,18 +136,24 @@ - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionVi return [self initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil]; } -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator +- (instancetype)initWithLayoutDelegate:(id)layoutDelegate layoutFacilitator:(id)layoutFacilitator { - __weak __typeof__(self) weakSelf = self; - ASDisplayNodeViewBlock collectionViewBlock = ^UIView *{ - __typeof__(self) strongSelf = weakSelf; - return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:layoutFacilitator eventLog:ASDisplayNodeGetEventLog(strongSelf)]; - }; + return [self initWithFrame:CGRectZero collectionViewLayout:[[ASCollectionLayout alloc] initWithLayoutDelegate:layoutDelegate] layoutFacilitator:layoutFacilitator]; +} - if (self = [super initWithViewBlock:collectionViewBlock]) { - return self; +- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator +{ + if (self = [super init]) { + // Must call the setter here to make sure pendingState is created and the layout is configured. + [self setCollectionViewLayout:layout]; + + __weak __typeof__(self) weakSelf = self; + [self setViewBlock:^{ + __typeof__(self) strongSelf = weakSelf; + return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:strongSelf->_pendingState.collectionViewLayout layoutFacilitator:layoutFacilitator eventLog:ASDisplayNodeGetEventLog(strongSelf)]; + }]; } - return nil; + return self; } #pragma mark ASDisplayNode @@ -147,12 +170,15 @@ - (void)didLoad self.pendingState = nil; view.asyncDelegate = pendingState.delegate; view.asyncDataSource = pendingState.dataSource; + view.inverted = pendingState.inverted; view.allowsSelection = pendingState.allowsSelection; view.allowsMultipleSelection = pendingState.allowsMultipleSelection; - - if (pendingState.rangeMode != ASLayoutRangeModeCount) { + + if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; } + + // Don't need to set collectionViewLayout to the view as the layout was already used to init the view in view block. } } @@ -167,18 +193,20 @@ - (void)clearContents [self.rangeController clearContents]; } -- (void)clearFetchedData -{ - [super clearFetchedData]; - [self.rangeController clearFetchedData]; -} - - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState { [super interfaceStateDidChange:newState fromState:oldState]; [ASRangeController layoutDebugOverlayIfNeeded]; } +- (void)didEnterPreloadState +{ + // Intentionally allocate the view here so that super will trigger a layout pass on it which in turn will trigger the intial data load. + // We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view. + [self view]; + [super didEnterPreloadState]; +} + #if ASRangeControllerLoggingEnabled - (void)didEnterVisibleState { @@ -193,12 +221,18 @@ - (void)didExitVisibleState } #endif +- (void)didExitPreloadState +{ + [super didExitPreloadState]; + [self.rangeController clearPreloadedData]; +} + #pragma mark Setter / Getter -// TODO: Implement this without the view. -- (ASCollectionDataController *)dataController +// TODO: Implement this without the view. Then revisit ASLayoutElementCollectionTableSetTraitCollection +- (ASDataController *)dataController { - return (ASCollectionDataController *)self.view.dataController; + return self.view.dataController; } // TODO: Implement this without the view. @@ -216,6 +250,26 @@ - (_ASCollectionPendingState *)pendingState return _pendingState; } +- (void)setInverted:(BOOL)inverted +{ + self.transform = inverted ? CATransform3DMakeScale(1, -1, 1) : CATransform3DIdentity; + if ([self pendingState]) { + _pendingState.inverted = inverted; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); + self.view.inverted = inverted; + } +} + +- (BOOL)inverted +{ + if ([self pendingState]) { + return _pendingState.inverted; + } else { + return self.view.inverted; + } +} + - (void)setDelegate:(id )delegate { if ([self pendingState]) { @@ -227,7 +281,7 @@ - (void)setDelegate:(id )delegate // and asserting here isn't an option – it is a common pattern for users to clear // the delegate/dataSource in dealloc, which may be running on a background thread. // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASCollectionView *view = (ASCollectionView *)_view; + ASCollectionView *view = self.view; ASPerformBlockOnMainThread(^{ view.asyncDelegate = delegate; }); @@ -253,7 +307,7 @@ - (void)setDataSource:(id )dataSource // and asserting here isn't an option – it is a common pattern for users to clear // the delegate/dataSource in dealloc, which may be running on a background thread. // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASCollectionView *view = (ASCollectionView *)_view; + ASCollectionView *view = self.view; ASPerformBlockOnMainThread(^{ view.asyncDataSource = dataSource; }); @@ -307,6 +361,42 @@ - (BOOL)allowsMultipleSelection } } +- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout +{ + if ([self pendingState]) { + [self _configureCollectionViewLayout:layout]; + _pendingState.collectionViewLayout = layout; + } else { + [self _configureCollectionViewLayout:layout]; + self.view.collectionViewLayout = layout; + } +} + +- (UICollectionViewLayout *)collectionViewLayout +{ + if ([self pendingState]) { + return _pendingState.collectionViewLayout; + } else { + return self.view.collectionViewLayout; + } +} + +- (ASElementMap *)visibleElements +{ + ASDisplayNodeAssertMainThread(); + // TODO Own the data controller when view is not yet loaded + return self.dataController.visibleMap; +} + +- (id)layoutDelegate +{ + UICollectionViewLayout *layout = self.collectionViewLayout; + if ([layout isKindOfClass:[ASCollectionLayout class]]) { + return ((ASCollectionLayout *)layout).layoutDelegate; + } + return nil; +} + #pragma mark - Range Tuning - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType @@ -392,13 +482,13 @@ - (void)reloadDataInitiallyIfNeeded - (NSInteger)numberOfItemsInSection:(NSInteger)section { [self reloadDataInitiallyIfNeeded]; - return [self.dataController numberOfRowsInSection:section]; + return [self.dataController.pendingMap numberOfItemsInSection:section]; } - (NSInteger)numberOfSections { [self reloadDataInitiallyIfNeeded]; - return [self.dataController numberOfSections]; + return self.dataController.pendingMap.numberOfSections; } - (NSArray<__kindof ASCellNode *> *)visibleNodes @@ -410,12 +500,12 @@ - (NSInteger)numberOfSections - (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath { [self reloadDataInitiallyIfNeeded]; - return [self.dataController nodeAtIndexPath:indexPath]; + return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node; } - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode { - return [self.dataController indexPathForNode:cellNode]; + return [self.dataController.pendingMap indexPathForElement:cellNode.collectionElement]; } - (NSArray *)indexPathsForVisibleItems @@ -458,7 +548,7 @@ - (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPa - (id)contextForSection:(NSInteger)section { ASDisplayNodeAssertMainThread(); - return [self.dataController contextForSection:section]; + return [self.dataController.pendingMap contextForSection:section]; } #pragma mark - Editing @@ -470,27 +560,43 @@ - (void)registerSupplementaryNodeOfKind:(NSString *)elementKind - (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion { - [self.view performBatchAnimated:animated updates:updates completion:completion]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view performBatchAnimated:animated updates:updates completion:completion]; + } else { + if (updates) { + updates(); + } + if (completion) { + completion(YES); + } + } } - (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion { - [self.view performBatchUpdates:updates completion:completion]; + [self performBatchAnimated:UIView.areAnimationsEnabled updates:updates completion:completion]; } - (void)waitUntilAllUpdatesAreCommitted { - [self.view waitUntilAllUpdatesAreCommitted]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view waitUntilAllUpdatesAreCommitted]; + } } - (void)reloadDataWithCompletion:(void (^)())completion { - [self.view reloadDataWithCompletion:completion]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view reloadDataWithCompletion:completion]; + } } - (void)reloadData { - [self.view reloadData]; + [self reloadDataWithCompletion:nil]; } - (void)relayoutItems @@ -505,7 +611,10 @@ - (void)reloadDataImmediately - (void)beginUpdates { - [self.dataController beginUpdates]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view beginUpdates]; + } } - (void)endUpdatesAnimated:(BOOL)animated @@ -515,47 +624,74 @@ - (void)endUpdatesAnimated:(BOOL)animated - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { - [self.dataController endUpdatesAnimated:animated completion:completion]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view endUpdatesAnimated:animated completion:completion]; + } } - (void)insertSections:(NSIndexSet *)sections { - [self.view insertSections:sections]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view insertSections:sections]; + } } - (void)deleteSections:(NSIndexSet *)sections { - [self.view deleteSections:sections]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view deleteSections:sections]; + } } - (void)reloadSections:(NSIndexSet *)sections { - [self.view reloadSections:sections]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view reloadSections:sections]; + } } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { - [self.view moveSection:section toSection:newSection]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view moveSection:section toSection:newSection]; + } } - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths { - [self.view insertItemsAtIndexPaths:indexPaths]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view insertItemsAtIndexPaths:indexPaths]; + } } - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths { - [self.view deleteItemsAtIndexPaths:indexPaths]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view deleteItemsAtIndexPaths:indexPaths]; + } } - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths { - [self.view reloadItemsAtIndexPaths:indexPaths]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view reloadItemsAtIndexPaths:indexPaths]; + } } - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { - [self.view moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; + } } #pragma mark - ASRangeControllerUpdateRangeProtocol @@ -569,9 +705,9 @@ - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; } } -#pragma mark ASEnvironment +#pragma mark - ASPrimitiveTraitCollection -ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock) +ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock) #pragma mark - Debugging (Private) @@ -583,4 +719,14 @@ - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; return result; } +#pragma mark - Private methods + +- (void)_configureCollectionViewLayout:(UICollectionViewLayout *)layout +{ + if ([layout isKindOfClass:[ASCollectionLayout class]]) { + ASCollectionLayout *collectionLayout = (ASCollectionLayout *)layout; + collectionLayout.collectionNode = self; + } +} + @end diff --git a/AsyncDisplayKit/ASCollectionView.h b/Source/ASCollectionView.h similarity index 83% rename from AsyncDisplayKit/ASCollectionView.h rename to Source/ASCollectionView.h index 1f6fb93c8d..ca1fe6a626 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/Source/ASCollectionView.h @@ -10,10 +10,12 @@ #import -#import #import #import #import +#import +#import +#import @class ASCellNode; @class ASCollectionNode; @@ -72,6 +74,20 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; +/** + * Similar to -indexPathForCell:. + * + * @param cellNode a cellNode in the collection view + * + * @return The index path for this cell node. + * + * @discussion This index path returned by this method is in the _view's_ index space + * and should only be used with @c ASCollectionView directly. To get an index path suitable + * for use with your data source and @c ASCollectionNode, call @c indexPathForNode: on the + * collection node instead. + */ +- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT; + /** * Similar to -supplementaryViewForElementKind:atIndexPath: * @@ -106,6 +122,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly) ASScrollDirection scrollableDirections; +/* + * A Boolean value that determines whether the nodes that the data source renders will be flipped. + */ +@property (nonatomic, assign) BOOL inverted; + +@end + +@interface ASCollectionView (Deprecated) + /** * Forces the .contentInset to be UIEdgeInsetsZero. * @@ -114,11 +139,7 @@ NS_ASSUME_NONNULL_BEGIN * automaticallyAdjustsScrollViewInsets, which may not be accessible. ASPagerNode uses this to ensure * its flow layout behaves predictably and does not log undefined layout warnings. */ -@property (nonatomic) BOOL zeroContentInsets; - -@end - -@interface ASCollectionView (Deprecated) +@property (nonatomic) BOOL zeroContentInsets ASDISPLAYNODE_DEPRECATED_MSG("Set automaticallyAdjustsScrollViewInsets=NO on your view controller instead."); /** * The object that acts as the asynchronous delegate of the collection view @@ -225,7 +246,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); /** * Perform a batch of updates asynchronously. This method must be called from the main thread. @@ -236,7 +257,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); /** * Reload everything from scratch, destroying the working range and all cached nodes. @@ -386,20 +407,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSArray<__kindof ASCellNode *> *)visibleNodes AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); -/** - * Similar to -indexPathForCell:. - * - * @param cellNode a cellNode in the collection view - * - * @return The index path for this cell node. - * - * @discussion This index path returned by this method is in the _view's_ index space - * and should only be used with @c ASCollectionView directly. To get an index path suitable - * for use with your data source and @c ASCollectionNode, call @c indexPathForNode: on the - * collection node instead. - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - @end ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDataSource.") @@ -411,33 +418,71 @@ ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegate.") @end /** - * Defines methods that let you coordinate with a `UICollectionViewFlowLayout` in combination with an `ASCollectionView`. + * Defines methods that let you coordinate a `UICollectionViewFlowLayout` in combination with an `ASCollectionNode`. */ -@protocol ASCollectionViewDelegateFlowLayout +@protocol ASCollectionDelegateFlowLayout @optional /** - * @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. + * Asks the delegate for the inset that should be applied to the given section. + * + * @see the same method in UICollectionViewDelegate. */ -- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("This method does nothing for 1.9.7+ due to incorrect implementation previously, see the header file for more information."); +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section; + +/** + * Asks the delegate for the size range that should be used to measure the header in the given flow layout section. + * + * @param collectionNode The sender. + * @param section The section. + * + * @return The size range for the header, or @c ASSizeRangeZero if there is no header in this section. + * + * If you want the header to completely determine its own size, return @c ASSizeRangeUnconstrained. + * + * @note Only the scrollable dimension of the returned size range will be used. In a vertical flow, + * only the height will be used. In a horizontal flow, only the width will be used. The other dimension + * will be constrained to fill the collection node. + * + * @discussion If you do not implement this method, ASDK will fall back to calling @c collectionView:layout:referenceSizeForHeaderInSection: + * and using that as the exact constrained size. If you don't implement that method, ASDK will read the @c headerReferenceSize from the layout. + */ +- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForHeaderInSection:(NSInteger)section; + +/** + * Asks the delegate for the size range that should be used to measure the footer in the given flow layout section. + * + * @param collectionNode The sender. + * @param section The section. + * + * @return The size range for the footer, or @c ASSizeRangeZero if there is no footer in this section. + * + * If you want the footer to completely determine its own size, return @c ASSizeRangeUnconstrained. + * + * @note Only the scrollable dimension of the returned size range will be used. In a vertical flow, + * only the height will be used. In a horizontal flow, only the width will be used. The other dimension + * will be constrained to fill the collection node. + * + * @discussion If you do not implement this method, ASDK will fall back to calling @c collectionView:layout:referenceSizeForFooterInSection: + * and using that as the exact constrained size. If you don't implement that method, ASDK will read the @c footerReferenceSize from the layout. + */ +- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section; /** * Asks the delegate for the size of the header in the specified section. */ -- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section; +- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForHeaderInSection: instead."); /** * Asks the delegate for the size of the footer in the specified section. */ -- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section; +- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForFooterInSection: instead."); + +@end +ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegateFlowLayout.") +@protocol ASCollectionViewDelegateFlowLayout @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASCollectionView.mm b/Source/ASCollectionView.mm similarity index 61% rename from AsyncDisplayKit/ASCollectionView.mm rename to Source/ASCollectionView.mm index 891ae2089a..f8b466bc78 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -8,24 +8,33 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASAssert.h" -#import "ASAvailability.h" -#import "ASBatchFetching.h" -#import "ASDelegateProxy.h" -#import "ASCellNode+Internal.h" -#import "ASCollectionDataController.h" -#import "ASCollectionViewLayoutController.h" -#import "ASCollectionViewFlowLayoutInspector.h" -#import "ASDisplayNodeExtras.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASInternalHelpers.h" -#import "UICollectionViewLayout+ASConvenience.h" -#import "ASRangeController.h" -#import "ASCollectionNode.h" -#import "_ASDisplayLayer.h" -#import "ASCollectionViewLayoutFacilitatorProtocol.h" -#import "ASSectionContext.h" -#import "ASCollectionView+Undeprecated.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import /** * A macro to get self.collectionNode and assign it to a local variable, or return @@ -57,99 +66,38 @@ typedef NS_ENUM(NSUInteger, ASCollectionViewInvalidationStyle) { }; static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone; -static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; -#pragma mark - -#pragma mark ASCellNode<->UICollectionViewCell bridging. - -@class _ASCollectionViewCell; - -@interface _ASCollectionViewCell : UICollectionViewCell -@property (nonatomic, weak) ASCellNode *node; -@property (nonatomic, strong) UICollectionViewLayoutAttributes *layoutAttributes; -@end - -@implementation _ASCollectionViewCell - -- (void)setNode:(ASCellNode *)node -{ - ASDisplayNodeAssertMainThread(); - node.layoutAttributes = _layoutAttributes; - _node = node; - [node __setSelectedFromUIKit:self.selected]; - [node __setHighlightedFromUIKit:self.highlighted]; -} - -- (void)setSelected:(BOOL)selected -{ - [super setSelected:selected]; - [_node __setSelectedFromUIKit:selected]; -} - -- (void)setHighlighted:(BOOL)highlighted -{ - [super setHighlighted:highlighted]; - [_node __setHighlightedFromUIKit:highlighted]; -} - -- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - _layoutAttributes = layoutAttributes; - _node.layoutAttributes = layoutAttributes; -} - -- (void)prepareForReuse -{ - self.layoutAttributes = nil; - - // Need to clear node pointer before UIKit calls setSelected:NO / setHighlighted:NO on its cells - self.node = nil; - [super prepareForReuse]; -} - -/** - * In the initial case, this is called by UICollectionView during cell dequeueing, before - * we get a chance to assign a node to it, so we must be sure to set these layout attributes - * on our node when one is next assigned to us in @c setNode: . Since there may be cases when we _do_ already - * have our node assigned e.g. during a layout update for existing cells, we also attempt - * to update it now. - */ -- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - self.layoutAttributes = layoutAttributes; -} - -@end +/// Used for all cells and supplementaries. UICV keys by supp-kind+reuseID so this is plenty. +static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; #pragma mark - #pragma mark ASCollectionView. -@interface ASCollectionView () { +@interface ASCollectionView () { ASCollectionViewProxy *_proxyDataSource; ASCollectionViewProxy *_proxyDelegate; - ASCollectionDataController *_dataController; + ASDataController *_dataController; ASRangeController *_rangeController; ASCollectionViewLayoutController *_layoutController; id _defaultLayoutInspector; __weak id _layoutInspector; NSMutableSet *_cellsForVisibilityUpdates; + NSMutableSet *_cellsForLayoutUpdates; id _layoutFacilitator; - BOOL _performingBatchUpdates; NSUInteger _superBatchUpdateCount; - NSMutableArray *_batchUpdateBlocks; - BOOL _isDeallocating; ASBatchContext *_batchContext; CGSize _lastBoundsSizeUsedForMeasuringNodes; - BOOL _ignoreNextBoundsSizeChangeForMeasuringNodes; NSMutableSet *_registeredSupplementaryKinds; CGPoint _deceleratingVelocity; + + BOOL _zeroContentInsets; ASCollectionViewInvalidationStyle _nextLayoutInvalidationStyle; @@ -174,12 +122,32 @@ @interface ASCollectionView () 0) { + // This assertion will be enabled soon. + // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); + return; + } + [_dataController waitUntilAllUpdatesAreCommitted]; } - (void)setDataSource:(id)dataSource { - // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. - ASDisplayNodeAssert(dataSource == nil, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property."); + // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. We also allow this when we're doing interop. + ASDisplayNodeAssert(_asyncDelegateFlags.interop || dataSource == nil, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property."); } - (void)setDelegate:(id)delegate { - // Our UIScrollView superclass sets its delegate to nil on dealloc. Only assert if we get a non-nil value here. - ASDisplayNodeAssert(delegate == nil, @"ASCollectionView uses asyncDelegate, not UICollectionView's delegate property."); + // Our UIScrollView superclass sets its delegate to nil on dealloc. Only assert if we get a non-nil value here. We also allow this when we're doing interop. + ASDisplayNodeAssert(_asyncDelegateFlags.interop || delegate == nil, @"ASCollectionView uses asyncDelegate, not UICollectionView's delegate property."); } - (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy @@ -408,8 +417,8 @@ - (void)setAsyncDataSource:(id)asyncDataSource if (asyncDataSource == nil) { _asyncDataSource = nil; _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - - memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags)); + _asyncDataSourceFlags = {}; + } else { _asyncDataSource = asyncDataSource; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; @@ -426,7 +435,15 @@ - (void)setAsyncDataSource:(id)asyncDataSource _asyncDataSourceFlags.collectionNodeNumberOfItemsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:numberOfItemsInSection:)]; _asyncDataSourceFlags.collectionNodeContextForSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:contextForSection:)]; _asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForSupplementaryElementOfKind:atIndexPath:)]; - + _asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)]; + _asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)]; + + _asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)]; + if (_asyncDataSourceFlags.interop) { + id interopDataSource = (id)_asyncDataSource; + _asyncDataSourceFlags.interopAlwaysDequeue = [[interopDataSource class] respondsToSelector:@selector(dequeuesCellsForNodeBackedItems)] && [[interopDataSource class] dequeuesCellsForNodeBackedItems]; + _asyncDataSourceFlags.interopViewForSupplementaryElement = [interopDataSource respondsToSelector:@selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:)]; + } ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection || _asyncDataSourceFlags.collectionViewNumberOfItemsInSection, @"Data source must implement collectionNode:numberOfItemsInSection:"); ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNodeBlockForItem @@ -435,6 +452,7 @@ - (void)setAsyncDataSource:(id)asyncDataSource || _asyncDataSourceFlags.collectionViewNodeForItem, @"Data source must implement collectionNode:nodeBlockForItemAtIndexPath: or collectionNode:nodeForItemAtIndexPath:"); } + _dataController.validationErrorSource = asyncDataSource; super.dataSource = (id)_proxyDataSource; //Cache results of layoutInspector to ensure flags are up to date if getter lazily loads a new one. @@ -463,14 +481,14 @@ - (void)setAsyncDelegate:(id)asyncDelegate if (asyncDelegate == nil) { _asyncDelegate = nil; _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - - memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags)); + _asyncDelegateFlags = {}; } else { _asyncDelegate = asyncDelegate; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; _asyncDelegateFlags.scrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; _asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; + _asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]; _asyncDelegateFlags.scrollViewWillBeginDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]; _asyncDelegateFlags.scrollViewDidEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]; _asyncDelegateFlags.collectionViewWillDisplayNodeForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNode:forItemAtIndexPath:)]; @@ -504,6 +522,12 @@ - (void)setAsyncDelegate:(id)asyncDelegate _asyncDelegateFlags.collectionNodeShouldShowMenuForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldShowMenuForItemAtIndexPath:)]; _asyncDelegateFlags.collectionNodeCanPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:canPerformAction:forItemAtIndexPath:sender:)]; _asyncDelegateFlags.collectionNodePerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:performAction:forItemAtIndexPath:sender:)]; + _asyncDelegateFlags.interop = [_asyncDelegate conformsToProtocol:@protocol(ASCollectionDelegateInterop)]; + if (_asyncDelegateFlags.interop) { + id interopDelegate = (id)_asyncDelegate; + _asyncDelegateFlags.interopWillDisplayCell = [interopDelegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)]; + _asyncDelegateFlags.interopDidEndDisplayingCell = [interopDelegate respondsToSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)]; + } } super.delegate = (id)_proxyDelegate; @@ -515,10 +539,13 @@ - (void)setAsyncDelegate:(id)asyncDelegate } } -- (void)setCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout +- (void)setCollectionViewLayout:(nonnull UICollectionViewLayout *)collectionViewLayout { + ASDisplayNodeAssertMainThread(); [super setCollectionViewLayout:collectionViewLayout]; + [self _configureCollectionViewLayout:collectionViewLayout]; + // Trigger recreation of layout inspector with new collection view layout if (_layoutInspector != nil) { _layoutInspector = nil; @@ -529,19 +556,14 @@ - (void)setCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout - (id)layoutInspector { if (_layoutInspector == nil) { - UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + UICollectionViewLayout *layout = self.collectionViewLayout; if (layout == nil) { // Layout hasn't been set yet, we're still init'ing return nil; } - - if ([layout asdk_isFlowLayout]) { - // Register the default layout inspector delegate for flow layouts only - _defaultLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self flowLayout:layout]; - } else { - // Register the default layout inspector delegate for custom collection view layouts - _defaultLayoutInspector = [[ASCollectionViewLayoutInspector alloc] initWithCollectionView:self]; - } + + _defaultLayoutInspector = [layout asdk_layoutInspector]; + ASDisplayNodeAssertNotNil(_defaultLayoutInspector, @"You must not return nil from -asdk_layoutInspector. Return [super asdk_layoutInspector] if you have to! Layout: %@", layout); // Explicitly call the setter to wire up the _layoutInspectorFlags self.layoutInspector = _defaultLayoutInspector; @@ -554,8 +576,17 @@ - (void)setLayoutInspector:(id)layoutInspector { _layoutInspector = layoutInspector; + _layoutInspectorFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath = [_layoutInspector respondsToSelector:@selector(collectionView:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:)]; + _layoutInspectorFlags.supplementaryNodesOfKindInSection = [_layoutInspector respondsToSelector:@selector(collectionView:supplementaryNodesOfKind:inSection:)]; _layoutInspectorFlags.didChangeCollectionViewDataSource = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDataSource:)]; _layoutInspectorFlags.didChangeCollectionViewDelegate = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDelegate:)]; + + if (_layoutInspectorFlags.didChangeCollectionViewDataSource) { + [_layoutInspector didChangeCollectionViewDataSource:self.asyncDataSource]; + } + if (_layoutInspectorFlags.didChangeCollectionViewDelegate) { + [_layoutInspector didChangeCollectionViewDelegate:self.asyncDelegate]; + } } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -578,47 +609,105 @@ - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)range return [_rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; } -- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath +- (void)setZeroContentInsets:(BOOL)zeroContentInsets +{ + _zeroContentInsets = zeroContentInsets; +} + +- (BOOL)zeroContentInsets { - return [[self nodeForItemAtIndexPath:indexPath] calculatedSize]; + return _zeroContentInsets; } -- (NSArray *> *)completedNodes +/// Uses latest size range from data source and -layoutThatFits:. +- (CGSize)sizeForElement:(ASCollectionElement *)element { - return [_dataController completedNodes]; + ASDisplayNodeAssertMainThread(); + if (element == nil) { + return CGSizeZero; + } + + NSString *supplementaryKind = element.supplementaryElementKind; + NSIndexPath *indexPath = [_dataController.visibleMap indexPathForElement:element]; + ASSizeRange sizeRange; + if (supplementaryKind == nil) { + sizeRange = [self dataController:_dataController constrainedSizeForNodeAtIndexPath:indexPath]; + } else { + sizeRange = [self dataController:_dataController constrainedSizeForSupplementaryNodeOfKind:supplementaryKind atIndexPath:indexPath]; + } + return [element.node layoutThatFits:sizeRange].size; +} + +- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath +{ + ASDisplayNodeAssertMainThread(); + + ASCollectionElement *e = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; + return [self sizeForElement:e]; } - (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath { - return [_dataController nodeAtCompletedIndexPath:indexPath]; + return [_dataController.visibleMap elementForItemAtIndexPath:indexPath].node; } - (NSIndexPath *)convertIndexPathFromCollectionNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait { + if (indexPath == nil) { + return nil; + } + // If this is a section index path, we don't currently have a method // to do a mapping. if (indexPath.item == NSNotFound) { return indexPath; } else { - ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; - NSIndexPath *viewIndexPath = [self indexPathForNode:node]; + NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap]; if (viewIndexPath == nil && wait) { [self waitUntilAllUpdatesAreCommitted]; - viewIndexPath = [self indexPathForNode:node]; + return [self convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:NO]; } return viewIndexPath; } } +/** + * Asserts that the index path is a valid view-index-path, and returns it if so, nil otherwise. + */ +- (nullable NSIndexPath *)validateIndexPath:(nullable NSIndexPath *)indexPath +{ + if (indexPath == nil) { + return nil; + } + + NSInteger section = indexPath.section; + if (section >= self.numberOfSections) { + ASDisplayNodeFailAssert(@"Collection view index path has invalid section %lu, section count = %lu", (unsigned long)section, (unsigned long)self.numberOfSections); + return nil; + } + + NSInteger item = indexPath.item; + // item == NSNotFound means e.g. "scroll to this section" and is acceptable + if (item != NSNotFound && item >= [self numberOfItemsInSection:section]) { + ASDisplayNodeFailAssert(@"Collection view index path has invalid item %lu in section %lu, item count = %lu", (unsigned long)indexPath.item, (unsigned long)section, (unsigned long)[self numberOfItemsInSection:section]); + return nil; + } + + return indexPath; +} + - (NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath { + if ([self validateIndexPath:indexPath] == nil) { + return nil; + } + // If this is a section index path, we don't currently have a method // to do a mapping. if (indexPath.item == NSNotFound) { return indexPath; } else { - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - return [_dataController indexPathForNode:node]; + return [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap]; } } @@ -641,12 +730,12 @@ - (NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath - (ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { - return [_dataController supplementaryNodeOfKind:elementKind atIndexPath:indexPath]; + return [_dataController.visibleMap supplementaryElementOfKind:elementKind atIndexPath:indexPath].node; } - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode { - return [_dataController completedIndexPathForNode:cellNode]; + return [_dataController.visibleMap indexPathForElement:cellNode.collectionElement]; } - (NSArray *)visibleNodes @@ -667,6 +756,14 @@ - (NSArray *)visibleNodes #pragma mark Internal +- (void)_configureCollectionViewLayout:(nonnull UICollectionViewLayout *)layout +{ + _hasDataControllerLayoutDelegate = [layout conformsToProtocol:@protocol(ASDataControllerLayoutDelegate)]; + if (_hasDataControllerLayoutDelegate) { + _dataController.layoutDelegate = (id)layout; + } +} + /** Performing nested batch updates with super (e.g. resizing a cell node & updating collection view during same frame) can cause super to throw data integrity exceptions because it checks the data source counts before @@ -690,93 +787,135 @@ - (ASDataController *)dataController return _dataController; } -- (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion +- (void)beginUpdates +{ + ASDisplayNodeAssertMainThread(); + // _changeSet must be available during batch update + ASDisplayNodeAssertTrue((_batchUpdateCount > 0) == (_changeSet != nil)); + + if (_batchUpdateCount == 0) { + _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[_dataController itemCountsFromDataSource]]; + } + _batchUpdateCount++; +} + +- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion { ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertNotNil(_changeSet, @"_changeSet must be available when batch update ends"); + + _batchUpdateCount--; + // Prevent calling endUpdatesAnimated:completion: in an unbalanced way + NSAssert(_batchUpdateCount >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); + + [_changeSet addCompletionHandler:completion]; - [_dataController beginUpdates]; + if (_batchUpdateCount == 0) { + _ASHierarchyChangeSet *changeSet = _changeSet; + // Nil out _changeSet before forwarding to _dataController to allow the change set to cause subsequent batch updates on the same run loop + _changeSet = nil; + changeSet.animated = animated; + [_dataController updateWithChangeSet:changeSet]; + } +} + +- (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion +{ + ASDisplayNodeAssertMainThread(); + [self beginUpdates]; if (updates) { updates(); } - [_dataController endUpdatesAnimated:animated completion:completion]; + [self endUpdatesAnimated:animated completion:completion]; } - (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion { - [self performBatchAnimated:YES updates:updates completion:completion]; + // We capture the current state of whether animations are enabled if they don't provide us with one. + [self performBatchAnimated:[UIView areAnimationsEnabled] updates:updates completion:completion]; } - (void)registerSupplementaryNodeOfKind:(NSString *)elementKind { ASDisplayNodeAssert(elementKind != nil, @"A kind is needed for supplementary node registration"); [_registeredSupplementaryKinds addObject:elementKind]; - [self registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:elementKind - withReuseIdentifier:[self __reuseIdentifierForKind:elementKind]]; + [self registerClass:[_ASCollectionReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:kReuseIdentifier]; } - (void)insertSections:(NSIndexSet *)sections { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController insertSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet insertSections:sections animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)deleteSections:(NSIndexSet *)sections { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController deleteSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet deleteSections:sections animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)reloadSections:(NSIndexSet *)sections { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController reloadSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet reloadSections:sections animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet moveSection:section toSection:newSection animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (id)contextForSection:(NSInteger)section { ASDisplayNodeAssertMainThread(); - return [_dataController contextForSection:section]; + return [_dataController.visibleMap contextForSection:section]; } - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController insertRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet insertItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet deleteItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet reloadItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; -} - -- (NSString *)__reuseIdentifierForKind:(NSString *)kind -{ - return [@"_ASCollectionSupplementaryView_" stringByAppendingString:kind]; + [self performBatchUpdates:^{ + [_changeSet moveItemAtIndexPath:indexPath toIndexPath:newIndexPath animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } #pragma mark - @@ -784,44 +923,110 @@ - (NSString *)__reuseIdentifierForKind:(NSString *)kind - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { - _superIsPendingDataLoad = NO; - return [_dataController completedNumberOfSections]; + if (_superIsPendingDataLoad) { + [_rangeController setNeedsUpdate]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:1]; + _superIsPendingDataLoad = NO; + } + return _dataController.visibleMap.numberOfSections; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - return [_dataController completedNumberOfRowsInSection:section]; + return [_dataController.visibleMap numberOfItemsInSection:section]; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { - return [[self nodeForItemAtIndexPath:indexPath] calculatedSize]; + ASDisplayNodeAssertMainThread(); + ASCellNode *cell = [self nodeForItemAtIndexPath:indexPath]; + if (cell.shouldUseUIKitCell) { + if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) { + CGSize size = [(id)_asyncDelegate collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:indexPath]; + cell.style.preferredSize = size; + return size; + } + } + ASCollectionElement *e = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; + return [self sizeForElement:e]; } -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForHeaderInSection:(NSInteger)section { - NSString *identifier = [self __reuseIdentifierForKind:kind]; - UICollectionReusableView *view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:identifier forIndexPath:indexPath]; - ASCellNode *node = [_dataController supplementaryNodeOfKind:kind atIndexPath:indexPath]; - ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self); - [_rangeController configureContentView:view forCellNode:node]; - return view; + ASDisplayNodeAssertMainThread(); + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; + ASCellNode *cell = [self supplementaryNodeForElementKind:UICollectionElementKindSectionHeader + atIndexPath:indexPath]; + if (cell.shouldUseUIKitCell && _asyncDelegateFlags.interop) { + if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]) { + return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForHeaderInSection:section]; + } + } + ASCollectionElement *e = [_dataController.visibleMap supplementaryElementOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath]; + return [self sizeForElement:e]; +} + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForFooterInSection:(NSInteger)section +{ + ASDisplayNodeAssertMainThread(); + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; + ASCellNode *cell = [self supplementaryNodeForElementKind:UICollectionElementKindSectionFooter + atIndexPath:indexPath]; + if (cell.shouldUseUIKitCell && _asyncDelegateFlags.interop) { + if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]) { + return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForFooterInSection:section]; + } + } + ASCollectionElement *e = [_dataController.visibleMap supplementaryElementOfKind:UICollectionElementKindSectionFooter atIndexPath:indexPath]; + return [self sizeForElement:e]; } +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + if ([_registeredSupplementaryKinds containsObject:kind] == NO) { + [self registerSupplementaryNodeOfKind:kind]; + } + + UICollectionReusableView *view = nil; + ASCellNode *node = [_dataController.visibleMap supplementaryElementOfKind:kind atIndexPath:indexPath].node; + + BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopViewForSupplementaryElement && (_asyncDataSourceFlags.interopAlwaysDequeue || node.shouldUseUIKitCell); + if (shouldDequeueExternally) { + // This codepath is used for both IGListKit mode, and app-level UICollectionView interop. + view = [(id)_asyncDataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; + } else { + ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self); + view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; + } + + if (_ASCollectionReusableView *reusableView = ASDynamicCast(view, _ASCollectionReusableView)) { + reusableView.node = node; + } + + if (node) { + [_rangeController configureContentView:view forCellNode:node]; + } + return view; +} - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - _ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:kCellReuseIdentifier forIndexPath:indexPath]; - + UICollectionViewCell *cell = nil; ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - cell.node = node; - [_rangeController configureContentView:cell.contentView forCellNode:node]; - - if (!AS_AT_LEAST_IOS8) { - // Even though UICV was introduced in iOS 6, and UITableView has always had the equivalent method, - // -willDisplayCell: was not introduced until iOS 8 for UICV. didEndDisplayingCell, however, is available. - [self collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; + + BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopAlwaysDequeue || (_asyncDataSourceFlags.interop && node.shouldUseUIKitCell); + if (shouldDequeueExternally) { + cell = [(id)_asyncDataSource collectionView:collectionView cellForItemAtIndexPath:indexPath]; + } else { + cell = [self dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; + } + + ASDisplayNodeAssert(node != nil, @"Cell node should exist. indexPath = %@, collectionDataSource = %@", indexPath, self); + + if (_ASCollectionViewCell *asCell = ASDynamicCast(cell, _ASCollectionViewCell)) { + asCell.node = node; + [_rangeController configureContentView:cell.contentView forCellNode:node]; } return cell; @@ -829,8 +1034,23 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + if (_asyncDelegateFlags.interopWillDisplayCell) { + [(id )_asyncDelegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; + } + + // Since _ASCollectionViewCell is not available for subclassing, this is faster than isKindOfClass: + // We must exit early here, because only _ASCollectionViewCell implements the -node accessor method. + if ([cell class] != [_ASCollectionViewCell class]) { + [_rangeController setNeedsUpdate]; + return; + } + ASCellNode *cellNode = [cell node]; cellNode.scrollView = collectionView; + + // Update the selected background view in collectionView:willDisplayCell:forItemAtIndexPath: otherwise it could be to + // early e.g. if the selectedBackgroundView was set in didLoad() + cell.selectedBackgroundView = cellNode.selectedBackgroundView; // Under iOS 10+, cells may be removed/re-added to the collection view without // receiving prepareForReuse/applyLayoutAttributes, as an optimization for e.g. @@ -843,10 +1063,8 @@ - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(_ASCo ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath); - if (_asyncDelegateFlags.collectionNodeWillDisplayItem) { - if (ASCollectionNode *collectionNode = self.collectionNode) { - [_asyncDelegate collectionNode:collectionNode willDisplayItemWithNode:cellNode]; - } + if (_asyncDelegateFlags.collectionNodeWillDisplayItem && self.collectionNode != nil) { + [_asyncDelegate collectionNode:self.collectionNode willDisplayItemWithNode:cellNode]; } else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -865,6 +1083,17 @@ - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(_ASCo - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + if (_asyncDelegateFlags.interopDidEndDisplayingCell) { + [(id )_asyncDelegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath]; + } + + // Since _ASCollectionViewCell is not available for subclassing, this is faster than isKindOfClass: + // We must exit early here, because only _ASCollectionViewCell implements the -node accessor method. + if ([cell class] != [_ASCollectionViewCell class]) { + [_rangeController setNeedsUpdate]; + return; + } + ASCellNode *cellNode = [cell node]; ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); @@ -887,8 +1116,14 @@ - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:( cell.layoutAttributes = nil; } -- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath +- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(_ASCollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { + // This is a safeguard similar to the behavior for cells in -[ASCollectionView collectionView:willDisplayCell:forItemAtIndexPath:] + // It ensures _ASCollectionReusableView receives layoutAttributes and calls applyLayoutAttributes. + if (view.layoutAttributes == nil) { + view.layoutAttributes = [collectionView layoutAttributesForSupplementaryElementOfKind:elementKind atIndexPath:indexPath]; + } + if (_asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath]; @@ -1080,6 +1315,7 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; if (ASInterfaceStateIncludesVisible(interfaceState)) { [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; + [self _checkForBatchFetching]; } for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) { @@ -1103,7 +1339,7 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi if (targetContentOffset != NULL) { ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; + [self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset]; } if (_asyncDelegateFlags.scrollViewWillEndDragging) { @@ -1111,6 +1347,15 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi } } +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView +{ + _deceleratingVelocity = CGPointZero; + + if (_asyncDelegateFlags.scrollViewDidEndDecelerating) { + [_asyncDelegate scrollViewDidEndDecelerating:scrollView]; + } +} + - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) { @@ -1185,10 +1430,13 @@ - (ASScrollDirection)flowLayoutScrollableDirections:(UICollectionViewFlowLayout - (void)layoutSubviews { - if (_zeroContentInsets) { - self.contentInset = UIEdgeInsetsZero; + if (_cellsForLayoutUpdates.count > 0) { + NSMutableArray *nodesSizesChanged = [NSMutableArray array]; + [_dataController relayoutNodes:_cellsForLayoutUpdates nodesSizeChanged:nodesSizesChanged]; + [self nodesDidRelayout:nodesSizesChanged]; } - + [_cellsForLayoutUpdates removeAllObjects]; + // Flush any pending invalidation action if needed. ASCollectionViewInvalidationStyle invalidationStyle = _nextLayoutInvalidationStyle; _nextLayoutInvalidationStyle = ASCollectionViewInvalidationStyleNone; @@ -1207,6 +1455,10 @@ - (void)layoutSubviews // To ensure _maxSizeForNodesConstrainedSize is up-to-date for every usage, this call to super must be done last [super layoutSubviews]; + + if (_zeroContentInsets) { + self.contentInset = UIEdgeInsetsZero; + } // Update range controller immediately if possible & needed. // Calling -updateIfNeeded in here with self.window == nil (early in the collection view's life) @@ -1244,9 +1496,10 @@ - (BOOL)canBatchFetch - (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes { // Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided - if (changes == 0) { + if (changes == 0 && _hasEverCheckedForBatchFetchingDueToUpdate) { return; } + _hasEverCheckedForBatchFetchingDueToUpdate = YES; // Push this to the next runloop to be sure the scroll view has the right content size dispatch_async(dispatch_get_main_queue(), ^{ @@ -1261,12 +1514,12 @@ - (void)_checkForBatchFetching return; } - [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; + [self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset]; } -- (void)_beginBatchFetchingIfNeededWithScrollView:(UIScrollView *)scrollView forScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset +- (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset { - if (ASDisplayShouldFetchBatchForScrollView(self, scrollDirection, contentOffset)) { + if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, self.scrollableDirections, contentOffset)) { [self _beginBatchFetching]; } } @@ -1289,49 +1542,51 @@ - (void)_beginBatchFetching } } - #pragma mark - ASDataControllerSource -- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { +- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath +{ ASCellNodeBlock block = nil; + ASCellNode *cell = nil; if (_asyncDataSourceFlags.collectionNodeNodeBlockForItem) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); block = [_asyncDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath]; } else if (_asyncDataSourceFlags.collectionNodeNodeForItem) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - ASCellNode *node = [_asyncDataSource collectionNode:collectionNode nodeForItemAtIndexPath:indexPath]; - if ([node isKindOfClass:[ASCellNode class]]) { - block = ^{ - return node; - }; - } else { - ASDisplayNodeFailAssert(@"Data source returned invalid node from tableNode:nodeForRowAtIndexPath:. Node: %@", node); - } - } else if (_asyncDataSourceFlags.collectionViewNodeBlockForItem) { + cell = [_asyncDataSource collectionNode:collectionNode nodeForItemAtIndexPath:indexPath]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" + } else if (_asyncDataSourceFlags.collectionViewNodeBlockForItem) { block = [_asyncDataSource collectionView:self nodeBlockForItemAtIndexPath:indexPath]; } else if (_asyncDataSourceFlags.collectionViewNodeForItem) { - ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; + cell = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; + } #pragma clang diagnostic pop - if ([node isKindOfClass:[ASCellNode class]]) { + + // Handle nil node block or cell + if (cell && [cell isKindOfClass:[ASCellNode class]]) { + block = ^{ + return cell; + }; + } + + if (block == nil) { + if (_asyncDataSourceFlags.interop) { block = ^{ - return node; + ASCellNode *cell = [[ASCellNode alloc] init]; + cell.shouldUseUIKitCell = YES; + cell.style.preferredSize = CGSizeZero; + return cell; }; } else { - ASDisplayNodeFailAssert(@"Data source returned invalid node from tableView:nodeForRowAtIndexPath:. Node: %@", node); + ASDisplayNodeFailAssert(@"ASCollection could not get a node block for row at index path %@: %@, %@. If you are trying to display a UICollectionViewCell, make sure your dataSource conforms to the protocol!", indexPath, cell, block); + block = ^{ + return [[ASCellNode alloc] init]; + }; } } - // Handle nil node block - if (block == nil) { - ASDisplayNodeFailAssert(@"ASTableNode could not get a node block for row at index path %@", indexPath); - block = ^{ - return [[ASCellNode alloc] init]; - }; - } - // Wrap the node block __weak __typeof__(self) weakSelf = self; return ^{ @@ -1341,16 +1596,14 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAt if (node.interactionDelegate == nil) { node.interactionDelegate = strongSelf; } + if (_inverted) { + node.transform = CATransform3DMakeScale(1, -1, 1) ; + } return node; }; return block; } -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; -} - - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section { if (_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection) { @@ -1380,18 +1633,31 @@ - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataControlle } } -- (id)dataControllerEnvironment +- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size +{ + NSIndexPath *indexPath = [self indexPathForNode:element.node]; + UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; + CGRect rect = attributes.frame; + return CGSizeEqualToSizeWithIn(rect.size, size, FLT_EPSILON); + +} + +- (id)dataControllerEnvironment { return self.collectionNode; } -#pragma mark - ASCollectionViewDataControllerSource +#pragma mark - ASDataControllerSource optional methods -- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + ASCellNodeBlock nodeBlock = nil; ASCellNode *node = nil; - if (_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, [[ASCellNode alloc] init] ); + if (_asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement) { + GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); + nodeBlock = [_asyncDataSource collectionNode:collectionNode nodeBlockForSupplementaryElementOfKind:kind atIndexPath:indexPath]; + } else if (_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement) { + GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); node = [_asyncDataSource collectionNode:collectionNode nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; } else if (_asyncDataSourceFlags.collectionViewNodeForSupplementaryElement) { #pragma clang diagnostic push @@ -1399,28 +1665,66 @@ - (ASCellNode *)dataController:(ASCollectionDataController *)dataController supp node = [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; #pragma clang diagnostic pop } - ASDisplayNodeAssert(node != nil, @"A node must be returned for supplementary element of kind '%@' at index path '%@'", kind, indexPath); - return node; + + if (nodeBlock == nil) { + if (node) { + nodeBlock = ^{ return node; }; + } else { + BOOL useUIKitCell = _asyncDataSourceFlags.interop; + nodeBlock = ^{ + ASCellNode *node = [[ASCellNode alloc] init]; + node.shouldUseUIKitCell = useUIKitCell; + return node; + }; + } + } + + return nodeBlock; +} + +- (NSArray *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections +{ + if (_asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection) { + NSMutableSet *kinds = [NSMutableSet set]; + GET_COLLECTIONNODE_OR_RETURN(collectionNode, @[]); + [sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL * _Nonnull stop) { + NSArray *kindsForSection = [_asyncDataSource collectionNode:collectionNode supplementaryElementKindsInSection:section]; + [kinds addObjectsFromArray:kindsForSection]; + }]; + return [kinds allObjects]; + } else { + // TODO: Lock this + return [_registeredSupplementaryKinds allObjects]; + } } -// TODO: Lock this -- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController +- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - return [_registeredSupplementaryKinds allObjects]; + return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; } -- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - return [self.layoutInspector collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; + if (_layoutInspectorFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath) { + return [self.layoutInspector collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; + } + + ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); + return ASSizeRangeMake(CGSizeZero, CGSizeZero); } -- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section +- (NSUInteger)dataController:(ASDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section { if (_asyncDataSource == nil) { return 0; } + + if (_layoutInspectorFlags.supplementaryNodesOfKindInSection) { + return [self.layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section]; + } - return [self.layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section]; + ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); + return 0; } - (id)dataController:(ASDataController *)dataController contextForSection:(NSInteger)section @@ -1446,34 +1750,74 @@ - (ASRangeController *)rangeController return _rangeController; } -- (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController +/// The UIKit version of this method is only available on iOS >= 9 +- (NSArray *)asdk_indexPathsForVisibleSupplementaryElementsOfKind:(NSString *)kind { - ASDisplayNodeAssertMainThread(); - // Calling -indexPathsForVisibleItems 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 = CGSizeEqualToSize(self.bounds.size, CGSizeZero); - return isZeroSized ? @[] : [self indexPathsForVisibleItems]; + if (NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_9_0) { + return [self indexPathsForVisibleSupplementaryElementsOfKind:kind]; + } + + // iOS 8 workaround + // We cannot use willDisplaySupplementaryView/didEndDisplayingSupplementaryView + // because those methods send index paths for _deleted items_ (invalid index paths) + [self layoutIfNeeded]; + NSArray *visibleAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:self.bounds]; + NSMutableArray *result = [NSMutableArray array]; + for (UICollectionViewLayoutAttributes *attributes in visibleAttributes) { + if (attributes.representedElementCategory == UICollectionElementCategorySupplementaryView + && [attributes.representedElementKind isEqualToString:kind]) { + [result addObject:attributes.indexPath]; + } + } + return result; } -- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController +- (NSArray *)visibleElementsForRangeController:(ASRangeController *)rangeController { - return self.scrollDirection; + if (CGRectIsEmpty(self.bounds)) { + return @[]; + } + + ASElementMap *map = _dataController.visibleMap; + NSMutableArray *result = [NSMutableArray array]; + + // Visible items + for (NSIndexPath *indexPath in self.indexPathsForVisibleItems) { + ASCollectionElement *element = [map elementForItemAtIndexPath:indexPath]; + if (element != nil) { + [result addObject:element]; + } else { + ASDisplayNodeFailAssert(@"Couldn't find 'visible' item at index path %@ in map %@", indexPath, map); + } + } + + // Visible supplementary elements + for (NSString *kind in map.supplementaryElementKinds) { + for (NSIndexPath *indexPath in [self asdk_indexPathsForVisibleSupplementaryElementsOfKind:kind]) { + ASCollectionElement *element = [map supplementaryElementOfKind:kind atIndexPath:indexPath]; + if (element != nil) { + [result addObject:element]; + } else { + ASDisplayNodeFailAssert(@"Couldn't find 'visible' supplementary element of kind %@ at index path %@ in map %@", kind, indexPath, map); + } + } + } + return result; } -- (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController +- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController { - ASDisplayNodeAssertMainThread(); - return self.bounds.size; + return _dataController.visibleMap; } -- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController +- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController { - return ASInterfaceStateForDisplayNode(self.collectionNode, self.window); + return self.scrollDirection; } -- (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath +- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController { - return [self nodeForItemAtIndexPath:indexPath]; + return ASInterfaceStateForDisplayNode(self.collectionNode, self.window); } - (NSString *)nameForRangeControllerDataSource @@ -1483,138 +1827,98 @@ - (NSString *)nameForRangeControllerDataSource #pragma mark - ASRangeControllerDelegate -- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController +- (void)rangeController:(ASRangeController *)rangeController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet { ASDisplayNodeAssertMainThread(); - _performingBatchUpdates = YES; -} - -- (void)rangeController:(ASRangeController *)rangeController didEndUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - if (completion) { - completion(NO); - } - _performingBatchUpdates = NO; - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - ASPerformBlockWithoutAnimation(!animated, ^{ - NSUInteger numberOfUpdateBlocks = _batchUpdateBlocks.count; - [_layoutFacilitator collectionViewWillPerformBatchUpdates]; - [self _superPerformBatchUpdates:^{ - for (dispatch_block_t block in _batchUpdateBlocks) { - block(); - } - } completion:^(BOOL finished){ - // Flush any range changes that happened as part of the update animations ending. - [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdateBlocks]; - if (completion) { completion(finished); } - }]; - // Flush any range changes that happened as part of submitting the update. - [_rangeController updateIfNeeded]; - }); - - [_batchUpdateBlocks removeAllObjects]; - _performingBatchUpdates = NO; -} - -- (void)didCompleteUpdatesInRangeController:(ASRangeController *)rangeController -{ - [self _checkForBatchFetching]; -} - -- (void)rangeController:(ASRangeController *)rangeController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); if (!self.asyncDataSource || _superIsPendingDataLoad) { return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates]; - if (_performingBatchUpdates) { - [_batchUpdateBlocks addObject:^{ - [super insertItemsAtIndexPaths:indexPaths]; - }]; - } else { - [UIView performWithoutAnimation:^{ - [super insertItemsAtIndexPaths:indexPaths]; - // Flush any range changes that happened as part of submitting the update. - [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; - }]; + if (changeSet.includesReloadData) { + //TODO Do we need to notify _layoutFacilitator? + return; } -} - -- (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES]; } - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates]; - if (_performingBatchUpdates) { - [_batchUpdateBlocks addObject:^{ - [super deleteItemsAtIndexPaths:indexPaths]; - }]; - } else { - [UIView performWithoutAnimation:^{ - [super deleteItemsAtIndexPaths:indexPaths]; - // Flush any range changes that happened as part of submitting the update. - [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; - }]; + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES]; } -} - -- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES]; } - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates]; - if (_performingBatchUpdates) { - [_batchUpdateBlocks addObject:^{ - [super insertSections:indexSet]; - }]; - } else { - [UIView performWithoutAnimation:^{ - [super insertSections:indexSet]; - // Flush any range changes that happened as part of submitting the update. - [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; - }]; + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES]; } } -- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)rangeController:(ASRangeController *)rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet { ASDisplayNodeAssertMainThread(); if (!self.asyncDataSource || _superIsPendingDataLoad) { + [changeSet executeCompletionHandlerWithFinished:NO]; return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates]; - if (_performingBatchUpdates) { - [_batchUpdateBlocks addObject:^{ - [super deleteSections:indexSet]; - }]; - } else { - [UIView performWithoutAnimation:^{ - [super deleteSections:indexSet]; + ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ + if(changeSet.includesReloadData) { + _superIsPendingDataLoad = YES; + [super reloadData]; + [changeSet executeCompletionHandlerWithFinished:YES]; + } else { + [_layoutFacilitator collectionViewWillPerformBatchUpdates]; + + __block NSUInteger numberOfUpdates = 0; + [self _superPerformBatchUpdates:^{ + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { + [super reloadItemsAtIndexPaths:change.indexPaths]; + numberOfUpdates++; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { + [super reloadSections:change.indexSet]; + numberOfUpdates++; + } + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { + [super deleteItemsAtIndexPaths:change.indexPaths]; + numberOfUpdates++; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { + [super deleteSections:change.indexSet]; + numberOfUpdates++; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { + [super insertSections:change.indexSet]; + numberOfUpdates++; + } + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { + [super insertItemsAtIndexPaths:change.indexPaths]; + numberOfUpdates++; + } + } completion:^(BOOL finished){ + // Flush any range changes that happened as part of the update animations ending. + [_rangeController updateIfNeeded]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates]; + [changeSet executeCompletionHandlerWithFinished:finished]; + }]; + // Flush any range changes that happened as part of submitting the update. [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; - }]; - } + } + }); } #pragma mark - ASCellNodeDelegate + - (void)nodeSelectedStateDidChange:(ASCellNode *)node { NSIndexPath *indexPath = [self indexPathForNode:node]; @@ -1635,6 +1939,12 @@ - (void)nodeHighlightedStateDidChange:(ASCellNode *)node } } +- (void)nodeDidInvalidateSize:(ASCellNode *)node +{ + [_cellsForLayoutUpdates addObject:node]; + [self setNeedsLayout]; +} + - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged { ASDisplayNodeAssertMainThread(); @@ -1642,51 +1952,56 @@ - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged if (!sizeChanged) { return; } + [self nodesDidRelayout:@[node]]; +} + +- (void)nodesDidRelayout:(NSArray *)nodes +{ + ASDisplayNodeAssertMainThread(); - NSIndexPath *uikitIndexPath = [self indexPathForNode:node]; - if (uikitIndexPath == nil) { + if (nodes.count == 0) { return; } - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[ uikitIndexPath ] batched:NO]; + NSMutableArray *uikitIndexPaths = [NSMutableArray arrayWithCapacity:nodes.count]; + for (ASCellNode *node in nodes) { + NSIndexPath *uikitIndexPath = [self indexPathForNode:node]; + if (uikitIndexPath != nil) { + [uikitIndexPaths addObject:uikitIndexPath]; + } + } + + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:uikitIndexPaths batched:NO]; ASCollectionViewInvalidationStyle invalidationStyle = _nextLayoutInvalidationStyle; - if (invalidationStyle == ASCollectionViewInvalidationStyleNone) { - [self setNeedsLayout]; - invalidationStyle = ASCollectionViewInvalidationStyleWithAnimation; - } - - // If we think we're going to animate, check if this node will prevent it. - if (invalidationStyle == ASCollectionViewInvalidationStyleWithAnimation) { - // TODO: Incorporate `shouldAnimateSizeChanges` into ASEnvironmentState for performance benefit. - static dispatch_once_t onceToken; - static BOOL (^shouldNotAnimateBlock)(ASDisplayNode *); - dispatch_once(&onceToken, ^{ - shouldNotAnimateBlock = ^BOOL(ASDisplayNode * _Nonnull node) { - return (node.shouldAnimateSizeChanges == NO); - }; - }); - if (ASDisplayNodeFindFirstNode(node, shouldNotAnimateBlock) != nil) { - // One single non-animated node causes the whole layout update to be non-animated - invalidationStyle = ASCollectionViewInvalidationStyleWithoutAnimation; + for (ASCellNode *node in nodes) { + if (invalidationStyle == ASCollectionViewInvalidationStyleNone) { + // We nodesDidRelayout also while we are in layoutSubviews. This should be no problem as CA will ignore this + // call while be in a layout pass + [self setNeedsLayout]; + invalidationStyle = ASCollectionViewInvalidationStyleWithAnimation; + } + + // If we think we're going to animate, check if this node will prevent it. + if (invalidationStyle == ASCollectionViewInvalidationStyleWithAnimation) { + // TODO: Incorporate `shouldAnimateSizeChanges` into ASEnvironmentState for performance benefit. + static dispatch_once_t onceToken; + static BOOL (^shouldNotAnimateBlock)(ASDisplayNode *); + dispatch_once(&onceToken, ^{ + shouldNotAnimateBlock = ^BOOL(ASDisplayNode * _Nonnull node) { + return (node.shouldAnimateSizeChanges == NO); + }; + }); + if (ASDisplayNodeFindFirstNode(node, shouldNotAnimateBlock) != nil) { + // One single non-animated node causes the whole layout update to be non-animated + invalidationStyle = ASCollectionViewInvalidationStyleWithoutAnimation; + break; + } } } - _nextLayoutInvalidationStyle = invalidationStyle; } -#pragma mark - Memory Management - -- (void)clearContents -{ - [_rangeController clearContents]; -} - -- (void)clearFetchedData -{ - [_rangeController clearFetchedData]; -} - #pragma mark - _ASDisplayView behavior substitutions // Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. // Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. @@ -1713,11 +2028,26 @@ - (void)didMoveToWindow [_rangeController setNeedsUpdate]; [_rangeController updateIfNeeded]; } + + // When we aren't visible, we will only fetch up to the visible area. Now that we are visible, + // we will fetch visible area + leading screens, so we need to check. + if (visible) { + [self _checkForBatchFetching]; + } } #pragma mark ASCALayerExtendedDelegate /** + * TODO: This code was added when we used @c calculatedSize as the size for + * items (e.g. collectionView:layout:sizeForItemAtIndexPath:) and so it + * was critical that we remeasured all nodes at this time. + * + * The assumption was that cv-bounds-size-change -> constrained-size-change, so + * this was the time when we get new constrained sizes for all items and remeasure + * them. However, the constrained sizes for items can be invalidated for many other + * reasons, hence why we never reuse the old constrained size anymore. + * * UICollectionView inadvertently triggers a -prepareLayout call to its layout object * between [super setFrame:] and [self layoutSubviews] during size changes. So we need * to get in there and re-measure our nodes before that -prepareLayout call. @@ -1727,6 +2057,10 @@ - (void)didMoveToWindow */ - (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds newValue:(CGRect)newBounds { + if (_hasDataControllerLayoutDelegate) { + // Let the layout delegate handle bounds changes if it's available. + return; + } if (self.collectionViewLayout == nil) { return; } @@ -1736,39 +2070,22 @@ - (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds new } _lastBoundsSizeUsedForMeasuringNodes = newBounds.size; - // First size change occurs during initial configuration. An expensive relayout pass is unnecessary at that time - // and should be avoided, assuming that the initial data loading automatically runs shortly afterward. - if (_ignoreNextBoundsSizeChangeForMeasuringNodes) { - _ignoreNextBoundsSizeChangeForMeasuringNodes = NO; - } else { - // Laying out all nodes is expensive, and performing an empty update may be unsafe - // if the data source has pending changes that it hasn't reported yet – the collection - // view will requery the new counts and expect them to match the previous counts. - // - // We only need to do this if the bounds changed in the non-scrollable direction. - // If, for example, a vertical flow layout has its height changed due to a status bar - // appearance update, we do not need to relayout all nodes. - // For a more permanent fix to the unsafety mentioned above, see https://github.com/facebook/AsyncDisplayKit/pull/2182 - ASScrollDirection scrollDirection = self.scrollableDirections; - BOOL fixedVertically = (ASScrollDirectionContainsVerticalDirection(scrollDirection) == NO); - BOOL fixedHorizontally = (ASScrollDirectionContainsHorizontalDirection(scrollDirection) == NO); - - BOOL changedInNonScrollingDirection = (fixedHorizontally && newBounds.size.width != lastUsedSize.width) || (fixedVertically && newBounds.size.height != lastUsedSize.height); - - if (changedInNonScrollingDirection) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // 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]; - // We need to ensure the size requery is done before we update our layout. - [self waitUntilAllUpdatesAreCommitted]; - [self.collectionViewLayout invalidateLayout]; - } -#pragma clang diagnostic pop + // Laying out all nodes is expensive. + // We only need to do this if the bounds changed in the non-scrollable direction. + // If, for example, a vertical flow layout has its height changed due to a status bar + // appearance update, we do not need to relayout all nodes. + // For a more permanent fix to the unsafety mentioned above, see https://github.com/facebook/AsyncDisplayKit/pull/2182 + ASScrollDirection scrollDirection = self.scrollableDirections; + BOOL fixedVertically = (ASScrollDirectionContainsVerticalDirection(scrollDirection) == NO); + BOOL fixedHorizontally = (ASScrollDirectionContainsHorizontalDirection(scrollDirection) == NO); + + BOOL changedInNonScrollingDirection = (fixedHorizontally && newBounds.size.width != lastUsedSize.width) || (fixedVertically && newBounds.size.height != lastUsedSize.height); + + if (changedInNonScrollingDirection) { + [_dataController relayoutAllNodes]; + [_dataController waitUntilAllUpdatesAreCommitted]; + // We need to ensure the size requery is done before we update our layout. + [self.collectionViewLayout invalidateLayout]; } } diff --git a/AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h b/Source/ASCollectionViewLayoutFacilitatorProtocol.h similarity index 97% rename from AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h rename to Source/ASCollectionViewLayoutFacilitatorProtocol.h index 7bd3261763..fd0b18ee72 100644 --- a/AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h +++ b/Source/ASCollectionViewLayoutFacilitatorProtocol.h @@ -9,6 +9,7 @@ // #pragma once +#import /** * This facilitator protocol is intended to help Layout to better diff --git a/AsyncDisplayKit/ASCollectionViewProtocols.h b/Source/ASCollectionViewProtocols.h similarity index 97% rename from AsyncDisplayKit/ASCollectionViewProtocols.h rename to Source/ASCollectionViewProtocols.h index 793d697744..c09fe87482 100644 --- a/AsyncDisplayKit/ASCollectionViewProtocols.h +++ b/Source/ASCollectionViewProtocols.h @@ -8,6 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import +#import + NS_ASSUME_NONNULL_BEGIN /** @@ -23,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Implement -numberOfSectionsInCollectionNode: instead."); -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement - collectionNode:viewForSupplementaryElementOfKind:atIndexPath: instead."); +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement - collectionNode:nodeForSupplementaryElementOfKind:atIndexPath: instead."); @end diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/Source/ASContextTransitioning.h similarity index 100% rename from AsyncDisplayKit/ASContextTransitioning.h rename to Source/ASContextTransitioning.h diff --git a/AsyncDisplayKit/ASControlNode+Subclasses.h b/Source/ASControlNode+Subclasses.h similarity index 97% rename from AsyncDisplayKit/ASControlNode+Subclasses.h rename to Source/ASControlNode+Subclasses.h index 79da77e4d7..bed6a1b33e 100644 --- a/AsyncDisplayKit/ASControlNode+Subclasses.h +++ b/Source/ASControlNode+Subclasses.h @@ -8,8 +8,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASControlNode.h" -#import "ASDisplayNode+Subclasses.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/ASControlNode.h b/Source/ASControlNode.h similarity index 86% rename from AsyncDisplayKit/ASControlNode.h rename to Source/ASControlNode.h index d4aa2ff070..b5e9935e28 100644 --- a/AsyncDisplayKit/ASControlNode.h +++ b/Source/ASControlNode.h @@ -34,6 +34,8 @@ typedef NS_OPTIONS(NSUInteger, ASControlNodeEvent) ASControlNodeEventTouchUpOutside = 1 << 5, /** A system event canceling the current touches for the control node. */ ASControlNodeEventTouchCancel = 1 << 6, + /** A system event triggered when controls like switches, slides, etc change state. */ + ASControlNodeEventValueChanged = 1 << 12, /** A system event when the Play/Pause button on the Apple TV remote is pressed. */ ASControlNodeEventPrimaryActionTriggered = 1 << 13, @@ -41,13 +43,19 @@ typedef NS_OPTIONS(NSUInteger, ASControlNodeEvent) ASControlNodeEventAllEvents = 0xFFFFFFFF }; -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 isSelected is set - ASControlStateReserved = 0xFF000000 // flags reserved for internal framework use -}; +/** + * Compatibility aliases for @c ASControlState enum. + * We previously provided our own enum, but when it was imported + * into Swift, the @c normal (0) option disappeared. + * + * Apple's UIControlState enum gets special treatment here, and + * UIControlStateNormal is available in Swift. + */ +typedef UIControlState ASControlState ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlState."); +static UIControlState const ASControlStateNormal ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateNormal.") = UIControlStateNormal; +static UIControlState const ASControlStateDisabled ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateDisabled.") = UIControlStateDisabled; +static UIControlState const ASControlStateHighlighted ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateHighlighted.") = UIControlStateHighlighted; +static UIControlState const ASControlStateSelected ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateSelected.") = UIControlStateSelected; /** @abstract ASControlNode is the base class for control nodes (such as buttons), or nodes that track touches to invoke targets with action messages. diff --git a/AsyncDisplayKit/ASControlNode.mm b/Source/ASControlNode.mm similarity index 84% rename from AsyncDisplayKit/ASControlNode.mm rename to Source/ASControlNode.mm index 32bb359475..cd20c7dab6 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/Source/ASControlNode.mm @@ -8,14 +8,15 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASControlNode.h" -#import "ASControlNode+Subclasses.h" -#import "ASImageNode.h" -#import "AsyncDisplayKit+Debug.h" -#import "ASInternalHelpers.h" -#import "ASControlTargetAction.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASLayoutElementInspectorNode.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import // UIControl allows dragging some distance outside of the control itself during // tracking. This value depends on the device idiom (25 or 70 points), so @@ -59,10 +60,17 @@ @interface ASControlNode () @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)); +/** + @abstract Returns the expanded bounds used to determine if a touch is considered 'inside' during tracking. + @param controlNode A control node. + @result The expanded bounds of the node. + */ +CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode); + + @end @implementation ASControlNode @@ -103,30 +111,39 @@ - (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled self.isAccessibilityElement = userInteractionEnabled; } +- (void)__exitHierarchy +{ + [super __exitHierarchy]; + + // If a control node is exit the hierarchy and is tracking we have to cancel it + if (self.tracking) { + [self _cancelTrackingWithEvent:nil]; + } +} #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-missing-super-calls" #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) + if (!self.enabled) { return; - - ASControlNodeEvent controlEventMask = 0; + } + + // Check if the tracking should start + UITouch *theTouch = [touches anyObject]; + if (![self beginTrackingWithTouch:theTouch withEvent:event]) { + return; + } // 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 - { + if (touches.count > 1 || self.tracking) { + [self _cancelTrackingWithEvent:event]; + } else { // Otherwise, begin tracking. self.tracking = YES; @@ -134,38 +151,39 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 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; + ASControlNodeEvent controlEventMask = (theTouch.tapCount == 1) ? ASControlNodeEventTouchDown : ASControlNodeEventTouchDownRepeat; + [self sendActionsForControlEvents:controlEventMask withEvent:event]; } - - [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) + if (!self.enabled) { return; + } - NSParameterAssert([touches count] == 1); + NSParameterAssert(touches.count == 1); UITouch *theTouch = [touches anyObject]; + + // Check if tracking should continue + if (!self.tracking || ![self continueTrackingWithTouch:theTouch withEvent:event]) { + self.tracking = NO; + return; + } + 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); + CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self); 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]; } @@ -173,35 +191,29 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. - if (!self.enabled) + 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]; + [self _cancelTrackingWithEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. - if (!self.enabled) + 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) + if (!self.tracking) { return; + } NSParameterAssert([touches count] == 1); UITouch *theTouch = [touches anyObject]; @@ -216,15 +228,38 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event [self endTrackingWithTouch:theTouch withEvent:event]; // Send the appropriate touch-up control event. - CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); + CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self); BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); [self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside) withEvent:event]; } +- (void)_cancelTrackingWithEvent:(UIEvent *)event +{ + // We're no longer tracking and there is no touch to be inside. + self.tracking = NO; + self.touchInside = NO; + self.highlighted = NO; + + // Send the cancel event. + [self sendActionsForControlEvents:ASControlNodeEventTouchCancel withEvent:event]; +} + #pragma clang diagnostic pop +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +{ + ASDisplayNodeAssertMainThread(); + + // If not enabled we should not care about receving touches + if (! self.enabled) { + return nil; + } + + return [super hitTest:point withEvent:event]; +} + - (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. @@ -239,13 +274,14 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer } #pragma mark - Action Messages + - (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask { NSParameterAssert(action); NSParameterAssert(controlEventMask != 0); - // This assertion would likely be helpful to users who aren't familiar with the implications of layer-backing. - // However, it would represent an API change (in debug) as it did not used to assert. - // ASDisplayNodeAssert(!self.isLayerBacked, @"ASControlNode is layer backed, will never be able to call target in target:action: pair."); + + // ASControlNode cannot be layer backed if adding a target + ASDisplayNodeAssert(!self.isLayerBacked, @"ASControlNode is layer backed, will never be able to call target in target:action: pair."); ASDN::MutexLocker l(_controlLock); @@ -376,6 +412,7 @@ - (void)removeTarget:(id)target action:(SEL)action forControlEvents:(ASControlNo } #pragma mark - + - (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event { NSParameterAssert(controlEvents != 0); @@ -420,15 +457,20 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v if (block == nil) { return; } - // 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){ + // Start with our first event (touch down) and work our way up to the last event (PrimaryActionTriggered) + for (ASControlNodeEvent thisEvent = ASControlNodeEventTouchDown; thisEvent <= ASControlNodeEventPrimaryActionTriggered; thisEvent <<= 1) { // If it's included in the mask, invoke the block. if ((mask & thisEvent) == thisEvent) block(thisEvent); } } +CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode) { + return CGRectInset(UIEdgeInsetsInsetRect(controlNode.view.bounds, controlNode.hitTestSlop), kASControlNodeExpandedInset, kASControlNodeExpandedInset); +} + #pragma mark - For Subclasses + - (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent { return YES; @@ -441,10 +483,12 @@ - (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEve - (void)cancelTrackingWithEvent:(UIEvent *)touchEvent { + // Subclass hook } - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent { + // Subclass hook } #pragma mark - Debug @@ -452,22 +496,4 @@ - (ASImageNode *)debugHighlightOverlay { return _debugHighlightOverlay; } - -// methods for visualizing ASLayoutSpecs -- (void)setHierarchyState:(ASHierarchyState)hierarchyState -{ - [super setHierarchyState:hierarchyState]; - - if (self.shouldVisualizeLayoutSpecs) { - [self addTarget:self action:@selector(inspectElement) forControlEvents:ASControlNodeEventTouchUpInside]; - } else { - [self removeTarget:self action:@selector(inspectElement) forControlEvents:ASControlNodeEventTouchUpInside]; - } -} - -- (void)inspectElement -{ - [ASLayoutElementInspectorNode sharedInstance].layoutElementToEdit = self; -} - @end diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/Source/ASDisplayNode+Beta.h similarity index 60% rename from AsyncDisplayKit/ASDisplayNode+Beta.h rename to Source/ASDisplayNode+Beta.h index e0a0e2696a..19a1e679ea 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/Source/ASDisplayNode+Beta.h @@ -8,9 +8,14 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDisplayNode.h" -#import "ASLayoutRangeType.h" -#import "ASEventLog.h" +#import +#import +#import +#import + +#if YOGA + #import YOGA_HEADER_PATH +#endif NS_ASSUME_NONNULL_BEGIN @@ -20,15 +25,15 @@ void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORI ASDISPLAYNODE_EXTERN_C_END #if ASEVENTLOG_ENABLE -#define ASDisplayNodeLogEvent(node, ...) [node.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__] + #define ASDisplayNodeLogEvent(node, ...) [node.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__] #else -#define ASDisplayNodeLogEvent(node, ...) + #define ASDisplayNodeLogEvent(node, ...) #endif #if ASEVENTLOG_ENABLE -#define ASDisplayNodeGetEventLog(node) node.eventLog + #define ASDisplayNodeGetEventLog(node) node.eventLog #else -#define ASDisplayNodeGetEventLog(node) nil + #define ASDisplayNodeGetEventLog(node) nil #endif /** @@ -58,8 +63,8 @@ typedef struct { * * This property defaults to NO. It will be removed in a future release. */ -+ (BOOL)suppressesInvalidCollectionUpdateExceptions AS_WARN_UNUSED_RESULT; -+ (void)setSuppressesInvalidCollectionUpdateExceptions:(BOOL)suppresses; ++ (BOOL)suppressesInvalidCollectionUpdateExceptions AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Collection update exceptions are thrown if assertions are enabled."); ++ (void)setSuppressesInvalidCollectionUpdateExceptions:(BOOL)suppresses ASDISPLAYNODE_DEPRECATED_MSG("Collection update exceptions are thrown if assertions are enabled."); /** * @abstract Recursively ensures node and all subnodes are displayed. @@ -123,6 +128,66 @@ typedef struct { */ + (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode; +/** + * @abstract Whether to draw all descendant nodes' layers/views into this node's layer/view's backing store. + * + * @discussion + * When set to YES, causes all descendant nodes' layers/views to be drawn directly into this node's layer/view's backing + * store. Defaults to NO. + * + * If a node's descendants are static (never animated or never change attributes after creation) then that node is a + * good candidate for rasterization. Rasterizing descendants has two main benefits: + * 1) Backing stores for descendant layers are not created. Instead the layers are drawn directly into the rasterized + * container. This can save a great deal of memory. + * 2) Since the entire subtree is drawn into one backing store, compositing and blending are eliminated in that subtree + * which can help improve animation/scrolling/etc performance. + * + * Rasterization does not currently support descendants with transform, sublayerTransform, or alpha. Those properties + * will be ignored when rasterizing descendants. + * + * Note: this has nothing to do with -[CALayer shouldRasterize], which doesn't work with ASDisplayNode's asynchronous + * rendering model. + */ +@property (nonatomic, assign) BOOL shouldRasterizeDescendants ASDISPLAYNODE_DEPRECATED_MSG("Deprecated in version 2.2"); + +@end + +#pragma mark - Yoga Layout Support + +#if YOGA + +extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node)); + +@interface ASDisplayNode (Yoga) + +@property (nonatomic, strong) NSArray *yogaChildren; +@property (nonatomic, strong) ASLayout *yogaCalculatedLayout; + +- (void)addYogaChild:(ASDisplayNode *)child; +- (void)removeYogaChild:(ASDisplayNode *)child; + +// These methods should not normally be called directly. +- (void)invalidateCalculatedYogaLayout; +- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize; + @end +@interface ASLayoutElementStyle (Yoga) + +@property (nonatomic, assign, readwrite) ASStackLayoutDirection direction; +@property (nonatomic, assign, readwrite) CGFloat spacing; +@property (nonatomic, assign, readwrite) ASStackLayoutJustifyContent justifyContent; +@property (nonatomic, assign, readwrite) ASStackLayoutAlignItems alignItems; +@property (nonatomic, assign, readwrite) YGPositionType positionType; +@property (nonatomic, assign, readwrite) ASEdgeInsets position; +@property (nonatomic, assign, readwrite) ASEdgeInsets margin; +@property (nonatomic, assign, readwrite) ASEdgeInsets padding; +@property (nonatomic, assign, readwrite) ASEdgeInsets border; +@property (nonatomic, assign, readwrite) CGFloat aspectRatio; +@property (nonatomic, assign, readwrite) YGWrap flexWrap; + +@end + +#endif + NS_ASSUME_NONNULL_END diff --git a/Source/ASDisplayNode+Convenience.h b/Source/ASDisplayNode+Convenience.h new file mode 100644 index 0000000000..b75947c0dd --- /dev/null +++ b/Source/ASDisplayNode+Convenience.h @@ -0,0 +1,27 @@ +// +// ASDisplayNode+Convenience.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/24/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class UIViewController; + +@interface ASDisplayNode (Convenience) + +/** + * @abstract Returns the view controller nearest to this node in the view hierarchy. + * + * @warning This property may only be accessed on the main thread. This property may + * be @c nil until the node's view is actually hosted in the view hierarchy. + */ +@property (nonatomic, nullable, readonly) __kindof UIViewController *closestViewController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ASDisplayNode+Convenience.m b/Source/ASDisplayNode+Convenience.m new file mode 100644 index 0000000000..a85b734338 --- /dev/null +++ b/Source/ASDisplayNode+Convenience.m @@ -0,0 +1,39 @@ +// +// ASDisplayNode+Convenience.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/24/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "ASDisplayNode+Convenience.h" + +#import + +#import +#import + +@implementation ASDisplayNode (Convenience) + +- (__kindof UIViewController *)closestViewController +{ + ASDisplayNodeAssertMainThread(); + + // Careful not to trigger node loading here. + if (!self.nodeLoaded) { + return nil; + } + + // Get the closest view. + UIView *view = ASFindClosestViewOfLayer(self.layer); + // Travel up the responder chain to find a view controller. + for (UIResponder *responder in [view asdk_responderChainEnumerator]) { + UIViewController *vc = ASDynamicCast(responder, UIViewController); + if (vc != nil) { + return vc; + } + } + return nil; +} + +@end diff --git a/AsyncDisplayKit/ASDisplayNode+Deprecated.h b/Source/ASDisplayNode+Deprecated.h similarity index 85% rename from AsyncDisplayKit/ASDisplayNode+Deprecated.h rename to Source/ASDisplayNode+Deprecated.h index ec7836d175..cb476d2f27 100644 --- a/AsyncDisplayKit/ASDisplayNode+Deprecated.h +++ b/Source/ASDisplayNode+Deprecated.h @@ -10,7 +10,7 @@ #pragma once -#import "ASDisplayNode.h" +#import @interface ASDisplayNode (Deprecated) @@ -115,4 +115,21 @@ ASLayoutElementStyleForwardingDeclaration */ @property (nonatomic, assign) BOOL usesImplicitHierarchyManagement ASDISPLAYNODE_DEPRECATED_MSG("Set .automaticallyManagesSubnodes instead."); +/** + * @abstract Indicates that the node should fetch any external data, such as images. + * + * @discussion Subclasses may override this method to be notified when they should begin to preload. Fetching + * should be done asynchronously. The node is also responsible for managing the memory of any data. + * The data may be remote and accessed via the network, but could also be a local database query. + */ +- (void)fetchData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterPreloadState instead."); + +/** + * Provides an opportunity to clear any fetched data (e.g. remote / network or database-queried) on the current node. + * + * @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearPreloadedData or + * selectively clear fetched data. + */ +- (void)clearFetchedData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didExitPreloadState instead."); + @end diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/Source/ASDisplayNode+Subclasses.h similarity index 92% rename from AsyncDisplayKit/ASDisplayNode+Subclasses.h rename to Source/ASDisplayNode+Subclasses.h index f6ed7b6f47..3d5e042494 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/Source/ASDisplayNode+Subclasses.h @@ -10,12 +10,10 @@ #import -#import -#import +#import #import -#import -@class ASLayoutSpec; +@class ASLayoutSpec, _ASDisplayLayer; NS_ASSUME_NONNULL_BEGIN @@ -39,7 +37,61 @@ NS_ASSUME_NONNULL_BEGIN * variables. */ -@interface ASDisplayNode (Subclassing) +@protocol ASInterfaceStateDelegate +@required + +/** + * @abstract Called whenever any bit in the ASInterfaceState bitfield is changed. + * @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; + +/** + * @abstract Called whenever the node becomes visible. + * @discussion Subclasses may use this to monitor when they become visible. + * @note This method is guaranteed to be called on main. + */ +- (void)didEnterVisibleState; + +/** + * @abstract Called whenever the node is no longer visible. + * @discussion Subclasses may use this to monitor when they are no longer visible. + * @note This method is guaranteed to be called on main. + */ +- (void)didExitVisibleState; + +/** + * @abstract Called whenever the the node has entered the display state. + * @discussion Subclasses may use this to monitor when a node should be rendering its content. + * @note This method is guaranteed to be called on main. + */ +- (void)didEnterDisplayState; + +/** + * @abstract Called whenever the the node has exited the display state. + * @discussion Subclasses may use this to monitor when a node should no longer be rendering its content. + * @note This method is guaranteed to be called on main. + */ +- (void)didExitDisplayState; + +/** + * @abstract Called whenever the the node has entered the preload state. + * @discussion Subclasses may use this to monitor data for a node should be preloaded, either from a local or remote source. + * @note This method is guaranteed to be called on main. + */ +- (void)didEnterPreloadState; + +/** + * @abstract Called whenever the the node has exited the preload state. + * @discussion Subclasses may use this to monitor whether preloading data for a node should be canceled. + * @note This method is guaranteed to be called on main. + */ +- (void)didExitPreloadState; + +@end + +@interface ASDisplayNode (Subclassing) #pragma mark - Properties /** @name Properties */ @@ -176,6 +228,32 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)invalidateCalculatedLayout; +#pragma mark - Observing Node State Changes +/** @name Observing node state changes */ + +/** + * Declare methods as requiring super calls (this can't be required in the protocol). + * For descriptions, see definition. + */ + +- (void)didEnterVisibleState ASDISPLAYNODE_REQUIRES_SUPER; +- (void)didExitVisibleState ASDISPLAYNODE_REQUIRES_SUPER; + +- (void)didEnterDisplayState ASDISPLAYNODE_REQUIRES_SUPER; +- (void)didExitDisplayState ASDISPLAYNODE_REQUIRES_SUPER; + +- (void)didEnterPreloadState ASDISPLAYNODE_REQUIRES_SUPER; +- (void)didExitPreloadState ASDISPLAYNODE_REQUIRES_SUPER; + +- (void)interfaceStateDidChange:(ASInterfaceState)newState + fromState:(ASInterfaceState)oldState ASDISPLAYNODE_REQUIRES_SUPER; + +/** + * @abstract Called when the node's ASTraitCollection changes + * + * @discussion Subclasses can override this method to react to a trait collection change. + */ +- (void)asyncTraitCollectionDidChange; #pragma mark - Drawing /** @name Drawing */ @@ -195,7 +273,7 @@ NS_ASSUME_NONNULL_BEGIN * @note Called on the display queue and/or main queue (MUST BE THREAD SAFE) */ + (void)drawRect:(CGRect)bounds withParameters:(nullable id )parameters - isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock + isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; /** @@ -212,7 +290,7 @@ NS_ASSUME_NONNULL_BEGIN * @note Called on the display queue and/or main queue (MUST BE THREAD SAFE) */ + (nullable UIImage *)displayWithParameters:(nullable id)parameters - isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock; + isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock; /** * @abstract Delegate override for drawParameters @@ -244,70 +322,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)displayDidFinish ASDISPLAYNODE_REQUIRES_SUPER; -/** @name Observing node-related changes */ - -/** - * @abstract Called whenever any bit in the ASInterfaceState bitfield is changed. - * - * @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 ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * @abstract Called whenever the node becomes visible. - * - * @discussion Subclasses may use this to monitor when they become visible. - * - * @note This method is guaranteed to be called on main. - */ -- (void)didEnterVisibleState ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * @abstract Called whenever the node is no longer visible. - * - * @discussion Subclasses may use this to monitor when they are no longer visible. - * - * @note This method is guaranteed to be called on main. - */ -- (void)didExitVisibleState ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * @abstract Called whenever the the node has entered the display state. - * - * @discussion Subclasses may use this to monitor when a node should be rendering its content. - * - * @note This method is guaranteed to be called on main. - */ -- (void)didEnterDisplayState ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * @abstract Called whenever the the node has exited the display state. - * - * @discussion Subclasses may use this to monitor when a node should no longer be rendering its content. - * - * @note This method is guaranteed to be called on main. - */ -- (void)didExitDisplayState ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * @abstract Called whenever the the node has entered the preload state. - * - * @discussion Subclasses may use this to monitor data for a node should be preloaded, either from a local or remote source. - * - * @note This method is guaranteed to be called on main. - */ -- (void)didEnterPreloadState ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * @abstract Called whenever the the node has exited the preload state. - * - * @discussion Subclasses may use this to monitor whether preloading data for a node should be canceled. - * - * @note This method is guaranteed to be called on main. - */ -- (void)didExitPreloadState ASDISPLAYNODE_REQUIRES_SUPER; - /** * Called just before the view is added to a window. */ @@ -323,28 +337,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly, assign, getter=isInHierarchy) BOOL inHierarchy; -/** - * @abstract Indicates that the node should fetch any external data, such as images. - * - * @discussion Subclasses may override this method to be notified when they should begin to fetch data. Fetching - * should be done asynchronously. The node is also responsible for managing the memory of any data. - * The data may be remote and accessed via the network, but could also be a local database query. - */ -- (void)fetchData ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * Provides an opportunity to clear any fetched data (e.g. remote / network or database-queried) on the current node. - * - * @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearFetchedData or - * selectively clear fetched data. - */ -- (void)clearFetchedData ASDISPLAYNODE_REQUIRES_SUPER; - /** * Provides an opportunity to clear backing store and other memory-intensive intermediates, such as text layout managers * on the current node. * - * @discussion Called by -recursivelyClearContents. Base class implements self.contents = nil, clearing any backing + * @discussion Called by -recursivelyClearContents. Always called on main thread. Base class implements self.contents = nil, clearing any backing * store, for asynchronous regeneration when needed. */ - (void)clearContents ASDISPLAYNODE_REQUIRES_SUPER; @@ -508,13 +505,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSString *)descriptionForRecursiveDescription; -/** - * @abstract Called when the node's ASTraitCollection changes - * - * @discussion Subclasses can override this method to react to a trait collection change. - */ -- (void)asyncTraitCollectionDidChange; - @end #define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created") diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm new file mode 100644 index 0000000000..d655a3daf2 --- /dev/null +++ b/Source/ASDisplayNode+Yoga.mm @@ -0,0 +1,437 @@ +// +// ASDisplayNode+Yoga.mm +// AsyncDisplayKit +// +// Created by Scott Goodson on 2/8/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#if YOGA /* YOGA */ + +#import +#import +#import +#import +#import + +#define YOGA_LAYOUT_LOGGING 0 + +extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node)) +{ + if (node == nil) { + return; + } + block(node); + for (ASDisplayNode *child in [node yogaChildren]) { + ASDisplayNodePerformBlockOnEveryYogaChild(child, block); + } +} + +#pragma mark - Yoga Type Conversion Helpers + +YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems); +YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent); +YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf); +YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction); +float yogaFloatForCGFloat(CGFloat value); +float yogaDimensionToPoints(ASDimension dimension); +float yogaDimensionToPercent(ASDimension dimension); +ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets); +YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, + float width, YGMeasureMode widthMode, + float height, YGMeasureMode heightMode); + +#define YGNODE_STYLE_SET_DIMENSION(yogaNode, property, dimension) \ + if (dimension.unit == ASDimensionUnitPoints) { \ + YGNodeStyleSet##property(yogaNode, yogaDimensionToPoints(dimension)); \ + } else if (dimension.unit == ASDimensionUnitFraction) { \ + YGNodeStyleSet##property##Percent(yogaNode, yogaDimensionToPercent(dimension)); \ + } else { \ + YGNodeStyleSet##property(yogaNode, YGUndefined); \ + }\ + +#define YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, property, dimension, edge) \ + if (dimension.unit == ASDimensionUnitPoints) { \ + YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \ + } else if (dimension.unit == ASDimensionUnitFraction) { \ + YGNodeStyleSet##property##Percent(yogaNode, edge, yogaDimensionToPercent(dimension)); \ + } else { \ + YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \ + } \ + +#define YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, property, dimension, edge) \ + if (dimension.unit == ASDimensionUnitPoints) { \ + YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \ + } else if (dimension.unit == ASDimensionUnitFraction) { \ + ASDisplayNodeAssert(NO, @"Unexpected Fraction value in applying ##property## values to YGNode"); \ + } else { \ + YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \ + } \ + +YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems) +{ + switch (alignItems) { + case ASStackLayoutAlignItemsNotSet: return YGAlignAuto; + case ASStackLayoutAlignItemsStart: return YGAlignFlexStart; + case ASStackLayoutAlignItemsEnd: return YGAlignFlexEnd; + case ASStackLayoutAlignItemsCenter: return YGAlignCenter; + case ASStackLayoutAlignItemsStretch: return YGAlignStretch; + case ASStackLayoutAlignItemsBaselineFirst: return YGAlignBaseline; + // FIXME: WARNING, Yoga does not currently support last-baseline item alignment. + case ASStackLayoutAlignItemsBaselineLast: return YGAlignBaseline; + } +} + +YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent) +{ + switch (justifyContent) { + case ASStackLayoutJustifyContentStart: return YGJustifyFlexStart; + case ASStackLayoutJustifyContentCenter: return YGJustifyCenter; + case ASStackLayoutJustifyContentEnd: return YGJustifyFlexEnd; + case ASStackLayoutJustifyContentSpaceBetween: return YGJustifySpaceBetween; + case ASStackLayoutJustifyContentSpaceAround: return YGJustifySpaceAround; + } +} + +YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf) +{ + switch (alignSelf) { + case ASStackLayoutAlignSelfStart: return YGAlignFlexStart; + case ASStackLayoutAlignSelfCenter: return YGAlignCenter; + case ASStackLayoutAlignSelfEnd: return YGAlignFlexEnd; + case ASStackLayoutAlignSelfStretch: return YGAlignStretch; + case ASStackLayoutAlignSelfAuto: return YGAlignAuto; + } +} + +YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction) +{ + return direction == ASStackLayoutDirectionVertical ? YGFlexDirectionColumn : YGFlexDirectionRow; +} + +float yogaFloatForCGFloat(CGFloat value) +{ + if (value < CGFLOAT_MAX / 2) { + return value; + } else { + return YGUndefined; + } +} + +float yogaDimensionToPoints(ASDimension dimension) +{ + ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitPoints, + @"Dimensions should not be type Fraction for this method: %f", dimension.value); + return yogaFloatForCGFloat(dimension.value); +} + +float yogaDimensionToPercent(ASDimension dimension) +{ + ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitFraction, + @"Dimensions should not be type Points for this method: %f", dimension.value); + return 100.0 * yogaFloatForCGFloat(dimension.value); + +} + +ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets) +{ + switch (edge) { + case YGEdgeLeft: return insets.left; + case YGEdgeTop: return insets.top; + case YGEdgeRight: return insets.right; + case YGEdgeBottom: return insets.bottom; + default: ASDisplayNodeCAssert(NO, @"YGEdge other than ASEdgeInsets is not supported."); + return ASDimensionAuto; + } +} + +YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasureMode widthMode, + float height, YGMeasureMode heightMode) +{ + id layoutElement = (__bridge id )YGNodeGetContext(yogaNode); + ASSizeRange sizeRange; + sizeRange.max = CGSizeMake(width, height); + sizeRange.min = sizeRange.max; + if (widthMode == YGMeasureModeAtMost) { + sizeRange.min.width = 0.0; + } + if (heightMode == YGMeasureModeAtMost) { + sizeRange.min.height = 0.0; + } + CGSize size = [[layoutElement layoutThatFits:sizeRange] size]; + return (YGSize){ .width = (float)size.width, .height = (float)size.height }; +} + +#pragma mark - ASDisplayNode+Yoga + +@interface ASDisplayNode (YogaInternal) +@property (nonatomic, weak) ASDisplayNode *yogaParent; +@property (nonatomic, assign) YGNodeRef yogaNode; +@end + +@implementation ASDisplayNode (Yoga) + +- (void)setYogaNode:(YGNodeRef)yogaNode +{ + _yogaNode = yogaNode; +} + +- (YGNodeRef)yogaNode +{ + if (_yogaNode == NULL) { + _yogaNode = YGNodeNew(); + } + return _yogaNode; +} + +- (void)setYogaParent:(ASDisplayNode *)yogaParent +{ + if (_yogaParent == yogaParent) { + return; + } + + YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed. + YGNodeRef oldParentRef = YGNodeGetParent(yogaNode); + if (oldParentRef != NULL) { + YGNodeRemoveChild(oldParentRef, yogaNode); + } + + _yogaParent = yogaParent; + if (yogaParent) { + self.hierarchyState |= ASHierarchyStateYogaLayoutEnabled; + YGNodeRef newParentRef = yogaParent.yogaNode; + YGNodeInsertChild(newParentRef, yogaNode, YGNodeGetChildCount(newParentRef)); + } else { + self.hierarchyState &= ~ASHierarchyStateYogaLayoutEnabled; + } +} + +- (ASDisplayNode *)yogaParent +{ + return _yogaParent; +} + +- (void)setYogaChildren:(NSArray *)yogaChildren +{ + for (ASDisplayNode *child in _yogaChildren) { + // Make sure to un-associate the YGNodeRef tree before replacing _yogaChildren + // If this becomes a performance bottleneck, it can be optimized by not doing the NSArray removals here. + [self removeYogaChild:child]; + } + _yogaChildren = nil; + for (ASDisplayNode *child in yogaChildren) { + [self addYogaChild:child]; + } +} + +- (NSArray *)yogaChildren +{ + return _yogaChildren; +} + +- (void)addYogaChild:(ASDisplayNode *)child +{ + if (child == nil) { + return; + } + if (_yogaChildren == nil) { + _yogaChildren = [NSMutableArray array]; + } + + // Clean up state in case this child had another parent. + [self removeYogaChild:child]; + + // YGNodeRef insertion is done in setParent: + child.yogaParent = self; + [_yogaChildren addObject:child]; + + self.hierarchyState |= ASHierarchyStateYogaLayoutEnabled; +} + +- (void)removeYogaChild:(ASDisplayNode *)child +{ + if (child == nil) { + return; + } + // YGNodeRef removal is done in setParent: + child.yogaParent = nil; + [_yogaChildren removeObjectIdenticalTo:child]; + + if (_yogaChildren.count == 0 && self.yogaParent == nil) { + self.hierarchyState &= ~ASHierarchyStateYogaLayoutEnabled; + } +} + +- (void)setYogaCalculatedLayout:(ASLayout *)yogaCalculatedLayout +{ + _yogaCalculatedLayout = yogaCalculatedLayout; +} + +- (ASLayout *)yogaCalculatedLayout +{ + return _yogaCalculatedLayout; +} + +- (ASLayout *)layoutForYogaNode +{ + YGNodeRef yogaNode = self.yogaNode; + + CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); + CGPoint position = CGPointMake(YGNodeLayoutGetLeft(yogaNode), YGNodeLayoutGetTop(yogaNode)); + + // TODO: If it were possible to set .flattened = YES, it would be valid to do so here. + return [ASLayout layoutWithLayoutElement:self size:size position:position sublayouts:nil]; +} + +- (void)setupYogaCalculatedLayout +{ + YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed. + uint32_t childCount = YGNodeGetChildCount(yogaNode); + ASDisplayNodeAssert(childCount == self.yogaChildren.count, + @"Yoga tree should always be in sync with .yogaNodes array! %@", self.yogaChildren); + + NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:childCount]; + for (ASDisplayNode *subnode in self.yogaChildren) { + [sublayouts addObject:[subnode layoutForYogaNode]]; + } + + // The layout for self should have position CGPointNull, but include the calculated size. + CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); + ASLayout *layout = [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts]; + self.yogaCalculatedLayout = layout; +} + +- (void)setYogaMeasureFuncIfNeeded +{ + // Manual size calculation via calculateSizeThatFits: + // This will be used for ASTextNode, as well as any other leaf node that has no layout spec. + if ((self.methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == NO + && self.layoutSpecBlock == NULL && self.yogaChildren.count == 0) { + YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed. + YGNodeSetContext(yogaNode, (__bridge void *)self); + YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc); + } +} + +- (void)invalidateCalculatedYogaLayout +{ + // Yoga internally asserts that this method may only be called on nodes with a measurement function. + YGNodeRef yogaNode = self.yogaNode; + if (YGNodeGetMeasureFunc(yogaNode)) { + YGNodeMarkDirty(yogaNode); + } +} + +- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize +{ + if (ASHierarchyStateIncludesYogaLayoutMeasuring(self.hierarchyState)) { + ASDisplayNodeAssert(NO, @"A Yoga layout is being performed by a parent; children must not perform their own until it is done! %@", [self displayNodeRecursiveDescription]); + return; + } + + ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { + node.hierarchyState |= ASHierarchyStateYogaLayoutMeasuring; + }); + + YGNodeRef rootYogaNode = self.yogaNode; + + // Apply the constrainedSize as a base, known frame of reference. + // If the root node also has style.*Size set, these will be overridden below. + // YGNodeCalculateLayout currently doesn't offer the ability to pass a minimum size (max is passed there). + YGNodeStyleSetMinWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.width)); + YGNodeStyleSetMinHeight(rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.height)); + + ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { + ASLayoutElementStyle *style = node.style; + YGNodeRef yogaNode = node.yogaNode; + + YGNodeStyleSetDirection (yogaNode, YGDirectionInherit); + + YGNodeStyleSetFlexWrap (yogaNode, style.flexWrap); + YGNodeStyleSetFlexGrow (yogaNode, style.flexGrow); + YGNodeStyleSetFlexShrink (yogaNode, style.flexShrink); + YGNODE_STYLE_SET_DIMENSION (yogaNode, FlexBasis, style.flexBasis); + + YGNodeStyleSetFlexDirection (yogaNode, yogaFlexDirection(style.direction)); + YGNodeStyleSetJustifyContent(yogaNode, yogaJustifyContent(style.justifyContent)); + YGNodeStyleSetAlignSelf (yogaNode, yogaAlignSelf(style.alignSelf)); + ASStackLayoutAlignItems alignItems = style.alignItems; + if (alignItems != ASStackLayoutAlignItemsNotSet) { + YGNodeStyleSetAlignItems(yogaNode, yogaAlignItems(alignItems)); + } + + YGNodeStyleSetPositionType (yogaNode, style.positionType); + ASEdgeInsets position = style.position; + ASEdgeInsets margin = style.margin; + ASEdgeInsets padding = style.padding; + ASEdgeInsets border = style.border; + + YGEdge edge = YGEdgeLeft; + for (int i = 0; i < 4; i++) { + YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Position, dimensionForEdgeWithEdgeInsets(edge, position), edge); + YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Margin, dimensionForEdgeWithEdgeInsets(edge, margin), edge); + YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Padding, dimensionForEdgeWithEdgeInsets(edge, padding), edge); + YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, Border, dimensionForEdgeWithEdgeInsets(edge, border), edge); + edge = (edge == YGEdgeLeft ? YGEdgeTop : (edge == YGEdgeTop ? YGEdgeRight : YGEdgeBottom)); + } + + CGFloat aspectRatio = style.aspectRatio; + if (aspectRatio > FLT_EPSILON && aspectRatio < CGFLOAT_MAX / 2.0) { + YGNodeStyleSetAspectRatio(yogaNode, aspectRatio); + } + + // For the root node, we use rootConstrainedSize above. For children, consult the style for their size. + if (node != self) { + YGNODE_STYLE_SET_DIMENSION(yogaNode, Width, style.width); + YGNODE_STYLE_SET_DIMENSION(yogaNode, Height, style.height); + + YGNODE_STYLE_SET_DIMENSION(yogaNode, MinWidth, style.minWidth); + YGNODE_STYLE_SET_DIMENSION(yogaNode, MinHeight, style.minHeight); + + YGNODE_STYLE_SET_DIMENSION(yogaNode, MaxWidth, style.maxWidth); + YGNODE_STYLE_SET_DIMENSION(yogaNode, MaxHeight, style.maxHeight); + } + + [node setYogaMeasureFuncIfNeeded]; + + /* TODO(appleguy): STYLE SETTER METHODS LEFT TO IMPLEMENT + void YGNodeStyleSetFlexDirection(YGNodeRef node, YGFlexDirection flexDirection); + void YGNodeStyleSetOverflow(YGNodeRef node, YGOverflow overflow); + void YGNodeStyleSetFlex(YGNodeRef node, float flex); + */ + }); + + // It is crucial to use yogaFloat... to convert CGFLOAT_MAX into YGUndefined here. + YGNodeCalculateLayout(rootYogaNode, + yogaFloatForCGFloat(rootConstrainedSize.max.width), + yogaFloatForCGFloat(rootConstrainedSize.max.height), + YGDirectionInherit); + + ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { + [node setupYogaCalculatedLayout]; + node.hierarchyState &= ~ASHierarchyStateYogaLayoutMeasuring; + }); + +#if YOGA_LAYOUT_LOGGING + // Concurrent layouts will interleave the NSLog messages unless we serialize. + // Use @synchornize rather than trampolining to the main thread so the tree state isn't changed. + @synchronized ([ASDisplayNode class]) { + NSLog(@"****************************************************************************"); + NSLog(@"******************** STARTING YOGA -> ASLAYOUT CREATION ********************"); + NSLog(@"****************************************************************************"); + ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { + NSLog(@" "); // Newline + NSLog(@"node = %@", node); + NSLog(@"style = %@", node.style); + NSLog(@"layout = %@", node.yogaCalculatedLayout); + YGNodePrint(node.yogaNode, (YGPrintOptions)(YGPrintOptionsStyle | YGPrintOptionsLayout)); + }); + } +#endif +} + +@end + +#endif /* YOGA */ diff --git a/AsyncDisplayKit/ASDisplayNode.h b/Source/ASDisplayNode.h similarity index 92% rename from AsyncDisplayKit/ASDisplayNode.h rename to Source/ASDisplayNode.h index 289b6246dd..97fcd97568 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -16,14 +16,15 @@ #import #import #import +#import #import -#import NS_ASSUME_NONNULL_BEGIN #define ASDisplayNodeLoggingEnabled 0 @class ASDisplayNode; +@protocol ASContextTransitioning; /** * UIView creation block. Used to create the backing view of a new display node. @@ -56,11 +57,19 @@ typedef void (^ASDisplayNodeContextModifier)(CGContextRef context); typedef ASLayoutSpec * _Nonnull(^ASLayoutSpecBlock)(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize); /** - Interface state is available on ASDisplayNode and ASViewController, and - allows checking whether a node is in an interface situation where it is prudent to trigger certain - actions: measurement, data loading, display, and visibility (the latter for animations or other onscreen-only effects). + * AsyncDisplayKit non-fatal error block. This block can be used for handling non-fatal errors. Useful for reporting + * errors that happens in production. */ +typedef void (^ASDisplayNodeNonFatalErrorBlock)(__kindof NSError * _Nonnull error); +/** + * Interface state is available on ASDisplayNode and ASViewController, and + * allows checking whether a node is in an interface situation where it is prudent to trigger certain + * actions: measurement, data loading, display, and visibility (the latter for animations or other onscreen-only effects). + * + * The defualt state, ASInterfaceStateNone, means that the element is not predicted to be onscreen soon and + * preloading should not be performed. Swift: use [] for the default behavior. + */ typedef NS_OPTIONS(NSUInteger, ASInterfaceState) { /** The element is not predicted to be onscreen soon and preloading should not be performed */ @@ -105,7 +114,7 @@ extern NSInteger const ASDefaultDrawingPriority; * */ -@interface ASDisplayNode : NSObject +@interface ASDisplayNode : NSObject /** @name Initializing a node object */ @@ -116,7 +125,7 @@ extern NSInteger const ASDefaultDrawingPriority; * @return An ASDisplayNode instance whose view will be a subclass that enables asynchronous rendering, and passes * through -layout and touch handling methods. */ -- (instancetype)init; +- (instancetype)init NS_DESIGNATED_INITIALIZER; /** @@ -174,6 +183,28 @@ extern NSInteger const ASDefaultDrawingPriority; */ - (void)onDidLoad:(ASDisplayNodeDidLoadBlock)body; +/** + * Set the block that should be used to load this node's view. + * + * @param viewBlock The block that creates a view for this node. + * + * @precondition The node is not yet loaded. + * + * @note You will usually NOT call this. See the limitations documented in @c initWithViewBlock: + */ +- (void)setViewBlock:(ASDisplayNodeViewBlock)viewBlock; + +/** + * Set the block that should be used to load this node's layer. + * + * @param layerBlock The block that creates a layer for this node. + * + * @precondition The node is not yet loaded. + * + * @note You will usually NOT call this. See the limitations documented in @c initWithLayerBlock: + */ +- (void)setLayerBlock:(ASDisplayNodeLayerBlock)layerBlock; + /** * @abstract Returns whether the node is synchronous. * @@ -250,6 +281,16 @@ extern NSInteger const ASDefaultDrawingPriority; */ @property (readonly) ASInterfaceState interfaceState; +/** + * @abstract Class property that allows to set a block that can be called on non-fatal errors. This + * property can be useful for cases when Async Display Kit can recover from an abnormal behavior, but + * still gives the opportunity to use a reporting mechanism to catch occurrences in production. In + * development, Async Display Kit will assert instead of calling this block. + * + * @warning This method is not thread-safe. + */ +@property (nonatomic, class, copy) ASDisplayNodeNonFatalErrorBlock nonFatalErrorBlock; + /** @name Managing dimensions */ @@ -416,28 +457,6 @@ extern NSInteger const ASDefaultDrawingPriority; */ @property (nonatomic, assign) BOOL displaysAsynchronously; -/** - * @abstract Whether to draw all descendant nodes' layers/views into this node's layer/view's backing store. - * - * @discussion - * When set to YES, causes all descendant nodes' layers/views to be drawn directly into this node's layer/view's backing - * store. Defaults to NO. - * - * If a node's descendants are static (never animated or never change attributes after creation) then that node is a - * good candidate for rasterization. Rasterizing descendants has two main benefits: - * 1) Backing stores for descendant layers are not created. Instead the layers are drawn directly into the rasterized - * container. This can save a great deal of memory. - * 2) Since the entire subtree is drawn into one backing store, compositing and blending are eliminated in that subtree - * which can help improve animation/scrolling/etc performance. - * - * Rasterization does not currently support descendants with transform, sublayerTransform, or alpha. Those properties - * will be ignored when rasterizing descendants. - * - * Note: this has nothing to do with -[CALayer shouldRasterize], which doesn't work with ASDisplayNode's asynchronous - * rendering model. - */ -@property (nonatomic, assign) BOOL shouldRasterizeDescendants; - /** * @abstract Prevent the node's layer from displaying. * @@ -478,31 +497,6 @@ extern NSInteger const ASDefaultDrawingPriority; */ - (void)recursivelyClearContents; -/** - * @abstract Calls -clearFetchedData on the receiver and its subnode hierarchy. - * - * @discussion Clears any memory-intensive fetched content. - * This method is used to notify the node that it should purge any content that is both expensive to fetch and to - * retain in memory. - * - * @see [ASDisplayNode(Subclassing) clearFetchedData] and [ASDisplayNode(Subclassing) fetchData] - */ -- (void)recursivelyClearFetchedData; - -/** - * @abstract Calls -fetchData on the receiver and its subnode hierarchy. - * - * @discussion Fetches content from remote sources for the current node and all subnodes. - * - * @see [ASDisplayNode(Subclassing) fetchData] and [ASDisplayNode(Subclassing) clearFetchedData] - */ -- (void)recursivelyFetchData; - -/** - * @abstract Triggers a recursive call to fetchData when the node has an interfaceState of ASInterfaceStatePreload - */ -- (void)setNeedsDataFetch; - /** * @abstract Toggle displaying a placeholder over the node that covers content until the node and all subnodes are * displayed. @@ -603,7 +597,7 @@ extern NSInteger const ASDefaultDrawingPriority; /** * Convenience methods for debugging. */ -@interface ASDisplayNode (Debugging) +@interface ASDisplayNode (Debugging) /** * @abstract Return a description of the node hierarchy. @@ -645,6 +639,11 @@ extern NSInteger const ASDefaultDrawingPriority; */ - (void)setNeedsLayout; +/** + * Performs a layout pass on the node. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread. + */ +- (void)layoutIfNeeded; + @property (nonatomic, strong, nullable) id contents; // default=nil @property (nonatomic, assign) BOOL clipsToBounds; // default==NO @property (nonatomic, getter=isOpaque) BOOL opaque; // default==YES @@ -780,7 +779,7 @@ extern NSInteger const ASDefaultDrawingPriority; * * @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. + * @param completion 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. If passed YES to shouldMeasureAsync it's guaranteed that measurement is happening on a background thread, otherwise measaurement will happen on the thread that the method was called on. The measurementCompletion callback is always called on the main thread right after the measurement and before -animateLayoutTransition:. @@ -801,7 +800,7 @@ extern NSInteger const ASDefaultDrawingPriority; * * @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. + * @param completion Optional completion block called only if a new layout is calculated. * * @see animateLayoutTransition: * diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/Source/ASDisplayNode.mm similarity index 72% rename from AsyncDisplayKit/ASDisplayNode.mm rename to Source/ASDisplayNode.mm index 202cbae8c2..8478e0a016 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -8,82 +8,68 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASDisplayNode+Beta.h" -#import "AsyncDisplayKit+Debug.h" +#import + +#import +#import +#import +#import +#import +#import #import -#import "_ASAsyncTransaction.h" -#import "_ASAsyncTransactionContainer+Private.h" -#import "_ASPendingState.h" -#import "_ASDisplayView.h" -#import "_ASScopeTimer.h" -#import "_ASCoreAnimationExtras.h" -#import "ASDisplayNodeExtras.h" -#import "ASTraitCollection.h" -#import "ASEqualityHelpers.h" -#import "ASRunLoopQueue.h" -#import "ASEnvironmentInternal.h" -#import "ASDimension.h" -#import "ASLayoutElementStylePrivate.h" - -#import "ASInternalHelpers.h" -#import "ASLayoutSpec+Subclasses.h" -#import "ASLayoutSpec.h" -#import "ASCellNode+Internal.h" -#import "ASWeakProxy.h" -#import "ASLayoutSpecPrivate.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import -#if DEBUG - #define AS_DEDUPE_LAYOUT_SPEC_TREE 1 +#if ASDisplayNodeLoggingEnabled + #define LOG(...) NSLog(__VA_ARGS__) +#else + #define LOG(...) #endif -NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; -NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; -NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp"; +// Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) +#if TIME_DISPLAYNODE_OPS + #define TIME_SCOPED(outVar) ASDN::ScopeTimer t(outVar) +#else + #define TIME_SCOPED(outVar) +#endif + +static ASDisplayNodeNonFatalErrorBlock _nonFatalErrorBlock = nil; // Forward declare CALayerDelegate protocol as the iOS 10 SDK moves CALayerDelegate from a formal delegate to a protocol. // We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10 @protocol CALayerDelegate; @interface ASDisplayNode () -{ - BOOL _shouldCacheLayoutSpec; - ASLayoutSpec *_layoutSpec; -} /** - * * See ASDisplayNodeInternal.h for ivars - * */ -- (void)_staticInitialize; - @end -#if ASDisplayNodeLoggingEnabled - #define LOG(...) NSLog(__VA_ARGS__) -#else - #define LOG(...) -#endif - -// Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) -#if TIME_DISPLAYNODE_OPS -#define TIME_SCOPED(outVar) ASDN::ScopeTimer t(outVar) -#else -#define TIME_SCOPED(outVar) -#endif - @implementation ASDisplayNode @dynamic layoutElementType; @synthesize debugName = _debugName; -@synthesize isFinalLayoutElement = _isFinalLayoutElement; @synthesize threadSafeBounds = _threadSafeBounds; @synthesize layoutSpecBlock = _layoutSpecBlock; @@ -184,21 +170,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (ASDisplayNodeSubclassOverridesSelector(c, @selector(layoutSpecThatFits:))) { overrides |= ASDisplayNodeMethodOverrideLayoutSpecThatFits; } + if (ASDisplayNodeSubclassOverridesSelector(c, @selector(fetchData))) { + overrides |= ASDisplayNodeMethodOverrideFetchData; + } + if (ASDisplayNodeSubclassOverridesSelector(c, @selector(clearFetchedData))) { + overrides |= ASDisplayNodeMethodOverrideClearFetchedData; + } return overrides; } - // At most a layoutSpecBlock or one of the three layout methods is overridden -#define __ASDisplayNodeCheckForLayoutMethodOverrides \ - ASDisplayNodeAssert(_layoutSpecBlock != NULL || \ - ((ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateSizeThatFits:)) ? 1 : 0) \ - + (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(layoutSpecThatFits:)) ? 1 : 0) \ - + (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateLayoutThatFits:)) ? 1 : 0)) <= 1, \ - @"Subclass %@ must at least provide a layoutSpecBlock or override at most one of the three layout methods: calculateLayoutThatFits:, layoutSpecThatFits:, or calculateSizeThatFits:", NSStringFromClass(self.class)) - + (void)initialize { [super initialize]; + if (self != [ASDisplayNode class]) { // Subclasses should never override these. Use unused to prevent warnings @@ -207,11 +192,11 @@ + (void)initialize ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedLayout)), @"Subclass %@ must not override calculatedLayout method.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measure:)), @"Subclass %@ must not override measure: method", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measureWithSizeRange:)), @"Subclass %@ must not override measureWithSizeRange: method. Instead overwrite calculateLayoutThatFits:", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:)), @"Subclass %@ must not override layoutThatFits: method. Instead overwrite calculateLayoutThatFits:.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:parentSize:)), @"Subclass %@ must not override layoutThatFits:parentSize method. Instead overwrite calculateLayoutThatFits:.", classString); + ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measureWithSizeRange:)), @"Subclass %@ must not override measureWithSizeRange: method. Instead override calculateLayoutThatFits:", classString); + ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:)), @"Subclass %@ must not override layoutThatFits: method. Instead override calculateLayoutThatFits:.", classString); + ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:parentSize:)), @"Subclass %@ must not override layoutThatFits:parentSize method. Instead override calculateLayoutThatFits:.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearFetchedData)), @"Subclass %@ must not override recursivelyClearFetchedData method.", classString); + ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearPreloadedData)), @"Subclass %@ must not override recursivelyClearFetchedData method.", classString); } // Below we are pre-calculating values per-class and dynamically adding a method (_staticInitialize) to populate these values @@ -239,9 +224,9 @@ + (void)initialize #if DEBUG // Check if subnodes where modified during the creation of the layout if (self == [ASDisplayNode class]) { - __block IMP originalLayoutSpecThatFitsIMP = ASReplaceMethodWithBlock(self, @selector(_layoutElementThatFits:), ^(ASDisplayNode *_self, ASSizeRange sizeRange) { + __block IMP originalLayoutSpecThatFitsIMP = ASReplaceMethodWithBlock(self, @selector(_locked_layoutElementThatFits:), ^(ASDisplayNode *_self, ASSizeRange sizeRange) { NSArray *oldSubnodes = _self.subnodes; - ASLayoutSpec *layoutElement = ((ASLayoutSpec *( *)(id, SEL, ASSizeRange))originalLayoutSpecThatFitsIMP)(_self, @selector(_layoutElementThatFits:), sizeRange); + ASLayoutSpec *layoutElement = ((ASLayoutSpec *( *)(id, SEL, ASSizeRange))originalLayoutSpecThatFitsIMP)(_self, @selector(_locked_layoutElementThatFits:), sizeRange); NSArray *subnodes = _self.subnodes; ASDisplayNodeAssert(oldSubnodes.count == subnodes.count, @"Adding or removing nodes in layoutSpecBlock or layoutSpecThatFits: is not allowed and can cause unexpected behavior."); for (NSInteger i = 0; i < oldSubnodes.count; i++) { @@ -275,26 +260,6 @@ + (Class)layerClass return [_ASDisplayLayer class]; } -+ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node -{ - static dispatch_once_t onceToken; - static ASRunLoopQueue *renderQueue; - dispatch_once(&onceToken, ^{ - renderQueue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() - andHandler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) { - [dequeuedItem _recursivelyTriggerDisplayAndBlock:NO]; - if (isQueueDrained) { - CFTimeInterval timestamp = CACurrentMediaTime(); - [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification - object:nil - userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}]; - } - }]; - }); - - [renderQueue enqueue:node]; -} - #pragma mark - Lifecycle - (void)_staticInitialize @@ -312,14 +277,14 @@ - (void)_initializeInstance _contentsScaleForDisplay = ASScreenScale(); - _environmentState = ASEnvironmentStateMakeDefault(); + _primitiveTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); _calculatedDisplayNodeLayout = std::make_shared(); _pendingDisplayNodeLayout = nullptr; _defaultLayoutTransitionDuration = 0.2; _defaultLayoutTransitionDelay = 0.0; - _defaultLayoutTransitionOptions = UIViewAnimationOptionBeginFromCurrentState; + _defaultLayoutTransitionOptions = UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionNone; _flags.canClearContentsOfLayer = YES; _flags.canCallSetNeedsDisplayOfLayer = YES; @@ -338,12 +303,11 @@ - (instancetype)init - (instancetype)initWithViewClass:(Class)viewClass { - if (!(self = [super init])) + if (!(self = [self init])) return nil; ASDisplayNodeAssert([viewClass isSubclassOfClass:[UIView class]], @"should initialize with a subclass of UIView"); - [self _initializeInstance]; _viewClass = viewClass; _flags.synchronous = ![viewClass isSubclassOfClass:[_ASDisplayView class]]; @@ -352,12 +316,12 @@ - (instancetype)initWithViewClass:(Class)viewClass - (instancetype)initWithLayerClass:(Class)layerClass { - if (!(self = [super init])) + if (!(self = [self init])) { return nil; + } ASDisplayNodeAssert([layerClass isSubclassOfClass:[CALayer class]], @"should initialize with a subclass of CALayer"); - [self _initializeInstance]; _layerClass = layerClass; _flags.synchronous = ![layerClass isSubclassOfClass:[_ASDisplayLayer class]]; _flags.layerBacked = YES; @@ -372,16 +336,13 @@ - (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock - (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock { - if (!(self = [super init])) + if (!(self = [self init])) { return nil; - - ASDisplayNodeAssertNotNil(viewBlock, @"should initialize with a valid block that returns a UIView"); - - [self _initializeInstance]; - _viewBlock = viewBlock; - _flags.synchronous = YES; + } + + [self setViewBlock:viewBlock]; if (didLoadBlock != nil) { - _onDidLoadBlocks = [NSMutableArray arrayWithObject:didLoadBlock]; + [self onDidLoad:didLoadBlock]; } return self; @@ -394,31 +355,46 @@ - (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock - (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock { - if (!(self = [super init])) + if (!(self = [self init])) { return nil; + } - ASDisplayNodeAssertNotNil(layerBlock, @"should initialize with a valid block that returns a CALayer"); - - [self _initializeInstance]; - _layerBlock = layerBlock; - _flags.synchronous = YES; - _flags.layerBacked = YES; + [self setLayerBlock:layerBlock]; if (didLoadBlock != nil) { - _onDidLoadBlocks = [NSMutableArray arrayWithObject:didLoadBlock]; + [self onDidLoad:didLoadBlock]; } return self; } +- (void)setViewBlock:(ASDisplayNodeViewBlock)viewBlock +{ + ASDisplayNodeAssertFalse(self.nodeLoaded); + ASDisplayNodeAssertNotNil(viewBlock, @"should initialize with a valid block that returns a UIView"); + + _viewBlock = viewBlock; + _flags.synchronous = YES; +} + +- (void)setLayerBlock:(ASDisplayNodeLayerBlock)layerBlock +{ + ASDisplayNodeAssertFalse(self.nodeLoaded); + ASDisplayNodeAssertNotNil(layerBlock, @"should initialize with a valid block that returns a CALayer"); + + _layerBlock = layerBlock; + _flags.synchronous = YES; + _flags.layerBacked = YES; +} + - (void)onDidLoad:(ASDisplayNodeDidLoadBlock)body { ASDN::MutexLocker l(__instanceLock__); - if ([self _isNodeLoaded]) { - ASDisplayNodeFailAssert(@"Attempt to call %@ on node after it was loaded. Node: %@", NSStringFromSelector(_cmd), self); - return; - } - - if (_onDidLoadBlocks == nil) { + + if ([self _locked_isNodeLoaded]) { + ASDisplayNodeAssertThreadAffinity(self); + ASDN::MutexUnlocker l(__instanceLock__); + body(self); + } else if (_onDidLoadBlocks == nil) { _onDidLoadBlocks = [NSMutableArray arrayWithObject:body]; } else { [_onDidLoadBlocks addObject:body]; @@ -442,7 +418,7 @@ - (void)dealloc // reference to subnodes. for (ASDisplayNode *subnode in _subnodes) - [subnode __setSupernode:nil]; + [subnode _setSupernode:nil]; // Trampoline any UIKit ivars' deallocation to main if (ASDisplayNodeThreadIsMain() == NO) { @@ -451,8 +427,14 @@ - (void)dealloc _subnodes = nil; +#if YOGA + if (_yogaNode != NULL) { + YGNodeFree(_yogaNode); + } +#endif + // TODO: Remove this? If supernode isn't already nil, this method isn't dealloc-safe anyway. - [self __setSupernode:nil]; + [self _setSupernode:nil]; } - (void)_scheduleIvarsForMainDeallocation @@ -483,8 +465,10 @@ - (void)_scheduleIvarsForMainDeallocation * up through ASDisplayNode, that we expect may need to be deallocated on main. * * This method caches its results. + * + * Result is of type NSValue<[Ivar]> */ -+ (NSValue/*<[Ivar]>*/ * _Nonnull)_ivarsThatMayNeedMainDeallocation ++ (NSValue * _Nonnull)_ivarsThatMayNeedMainDeallocation { static NSCache *ivarsCache; static dispatch_once_t onceToken; @@ -524,7 +508,7 @@ - (void)_scheduleIvarsForMainDeallocation Ivar ivar = allMyIvars[i]; const char *type = ivar_getTypeEncoding(ivar); - if (strcmp(type, @encode(id)) == 0) { + if (type != NULL && strcmp(type, @encode(id)) == 0) { // If it's `id` we have to include it just in case. resultIvars[resultCount] = ivar; resultCount += 1; @@ -552,7 +536,7 @@ - (void)_scheduleIvarsForMainDeallocation return result; } -#pragma mark - Core +#pragma mark - Loading / Unloading - (void)__unloadNode { @@ -561,11 +545,12 @@ - (void)__unloadNode ASDisplayNodeAssert(_flags.synchronous == NO, @"Node created using -initWithViewBlock:/-initWithLayerBlock: cannot be unloaded. Node: %@", self); ASDN::MutexLocker l(__instanceLock__); - if (_flags.layerBacked) + if (_flags.layerBacked) { _pendingViewState = [_ASPendingState pendingViewStateFromLayer:_layer]; - else + } else { _pendingViewState = [_ASPendingState pendingViewStateFromView:_view]; - + } + [_view removeFromSuperview]; _view = nil; if (_flags.layerBacked) @@ -574,21 +559,14 @@ - (void)__unloadNode _layer = nil; } -- (void)__loadNode -{ - [self layer]; -} - -- (BOOL)__shouldLoadViewOrLayer +- (BOOL)_locked_shouldLoadViewOrLayer { - return !(_hierarchyState & ASHierarchyStateRasterized); + return !_flags.isDeallocating && !(_hierarchyState & ASHierarchyStateRasterized); } -- (UIView *)_viewToLoad +- (UIView *)_locked_viewToLoad { - UIView *view; - ASDN::MutexLocker l(__instanceLock__); - + UIView *view = nil; if (_viewBlock) { view = _viewBlock(); ASDisplayNodeAssertNotNil(view, @"View block returned nil"); @@ -602,21 +580,29 @@ - (UIView *)_viewToLoad view = [[_viewClass alloc] init]; } - // Update flags related to special handling of UIImageView layers. More details on the flags - if (_flags.synchronous && ([_viewClass isSubclassOfClass:[UIImageView class]] || [_viewClass isSubclassOfClass:[UIActivityIndicatorView class]])) { - _flags.canClearContentsOfLayer = NO; - _flags.canCallSetNeedsDisplayOfLayer = NO; + // Special handling of wrapping UIKit components + if (_flags.synchronous) { + // UIImageView layers. More details on the flags + if ([_viewClass isSubclassOfClass:[UIImageView class]]) { + _flags.canClearContentsOfLayer = NO; + _flags.canCallSetNeedsDisplayOfLayer = NO; + } + + // UIActivityIndicator + if ([_viewClass isSubclassOfClass:[UIActivityIndicatorView class]] + || [_viewClass isSubclassOfClass:[UIVisualEffectView class]]) { + self.opaque = NO; + } } return view; } -- (CALayer *)_layerToLoad +- (CALayer *)_locked_layerToLoad { - CALayer *layer; - ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssert(_flags.layerBacked, @"_layerToLoad is only for layer-backed nodes"); + CALayer *layer = nil; if (_layerBlock) { layer = _layerBlock(); ASDisplayNodeAssertNotNil(layer, @"Layer block returned nil"); @@ -633,23 +619,13 @@ - (CALayer *)_layerToLoad return layer; } -- (void)_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked +- (void)_locked_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked { - ASDN::MutexLocker l(__instanceLock__); - - if (_flags.isDeallocating) { - return; - } - - if (![self __shouldLoadViewOrLayer]) { - return; - } - if (isLayerBacked) { TIME_SCOPED(_debugTimeToCreateView); - _layer = [self _layerToLoad]; + _layer = [self _locked_layerToLoad]; static int ASLayerDelegateAssociationKey; - + /** * CALayer's .delegate property is documented to be weak, but the implementation is actually assign. * Because our layer may survive longer than the node (e.g. if someone else retains it, or if the node @@ -661,65 +637,39 @@ - (void)_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked objc_setAssociatedObject(_layer, &ASLayerDelegateAssociationKey, instance, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } else { TIME_SCOPED(_debugTimeToCreateView); - _view = [self _viewToLoad]; + _view = [self _locked_viewToLoad]; _view.asyncdisplaykit_node = self; _layer = _view.layer; } _layer.asyncdisplaykit_node = self; - - self.asyncLayer.asyncDelegate = self; - - { - TIME_SCOPED(_debugTimeToApplyPendingState); - [self _applyPendingStateToViewOrLayer]; - } - { - TIME_SCOPED(_debugTimeToAddSubnodeViews); - [self _addSubnodeViewsAndLayers]; - } - { - TIME_SCOPED(_debugTimeForDidLoad); - [self __didLoad]; - } -} - -- (UIView *)view -{ - ASDisplayNodeAssert(!_flags.layerBacked, @"Call to -view undefined on layer-backed nodes"); - if (_flags.layerBacked) { - return nil; - } - if (!_view) { - ASDisplayNodeAssertMainThread(); - [self _loadViewOrLayerIsLayerBacked:NO]; - } - return _view; + + self._locked_asyncLayer.asyncDelegate = self; } -- (CALayer *)layer +- (void)_didLoad { - if (!_layer) { - ASDisplayNodeAssertMainThread(); - - if (!_flags.layerBacked) { - return self.view.layer; - } - [self _loadViewOrLayerIsLayerBacked:YES]; + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + ASDisplayNodeLogEvent(self, @"didLoad"); + TIME_SCOPED(_debugTimeForDidLoad); + + [self didLoad]; + + __instanceLock__.lock(); + NSArray *onDidLoadBlocks = [_onDidLoadBlocks copy]; + _onDidLoadBlocks = nil; + __instanceLock__.unlock(); + + for (ASDisplayNodeDidLoadBlock block in onDidLoadBlocks) { + block(self); } - return _layer; } -// Returns nil if our view is not an _ASDisplayView, but will create it if necessary. -- (_ASDisplayView *)ensureAsyncView -{ - return _flags.synchronous ? nil : (_ASDisplayView *)self.view; -} - -// Returns nil if the layer is not an _ASDisplayLayer; will not create the layer if nil. -- (_ASDisplayLayer *)asyncLayer +- (void)didLoad { - ASDN::MutexLocker l(__instanceLock__); - return [_layer isKindOfClass:[_ASDisplayLayer class]] ? (_ASDisplayLayer *)_layer : nil; + ASDisplayNodeAssertMainThread(); + + // Subclass hook } - (BOOL)isNodeLoaded @@ -727,45 +677,145 @@ - (BOOL)isNodeLoaded if (ASDisplayNodeThreadIsMain()) { // Because the view and layer can only be created and destroyed on Main, that is also the only thread // where the state of this property can change. As an optimization, we can avoid locking. - return [self _isNodeLoaded]; + return [self _locked_isNodeLoaded]; } else { ASDN::MutexLocker l(__instanceLock__); - return [self _isNodeLoaded]; + return [self _locked_isNodeLoaded]; } } -- (BOOL)_isNodeLoaded +- (BOOL)_locked_isNodeLoaded { return (_view != nil || (_layer != nil && _flags.layerBacked)); } -- (NSString *)debugName +#pragma mark - Misc Setter / Getter + +- (UIView *)view { ASDN::MutexLocker l(__instanceLock__); - return _debugName; + + ASDisplayNodeAssert(!_flags.layerBacked, @"Call to -view undefined on layer-backed nodes"); + BOOL isLayerBacked = _flags.layerBacked; + if (isLayerBacked) { + return nil; + } + + if (_view != nil) { + return _view; + } + + if (![self _locked_shouldLoadViewOrLayer]) { + return nil; + } + + // Loading a view needs to happen on the main thread + ASDisplayNodeAssertMainThread(); + [self _locked_loadViewOrLayerIsLayerBacked:isLayerBacked]; + + // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout + // but automatic subnode management would require us to modify the node tree + // in the background on a loaded node, which isn't currently supported. + if (_pendingViewState.hasSetNeedsLayout) { + // Need to unlock before calling setNeedsLayout to avoid deadlocks. + // MutexUnlocker will re-lock at the end of scope. + ASDN::MutexUnlocker u(__instanceLock__); + [self __setNeedsLayout]; + } + + [self _locked_applyPendingStateToViewOrLayer]; + + { + // The following methods should not be called with a lock + ASDN::MutexUnlocker u(__instanceLock__); + + // No need for the lock as accessing the subviews or layers are always happening on main + [self _addSubnodeViewsAndLayers]; + + // A subclass hook should never be called with a lock + [self _didLoad]; + } + + return _view; } -- (void)setDebugName:(NSString *)debugName +- (CALayer *)layer { ASDN::MutexLocker l(__instanceLock__); - if (!ASObjectIsEqual(_debugName, debugName)) { - _debugName = [debugName copy]; + if (_layer != nil) { + return _layer; + } + + BOOL isLayerBacked = _flags.layerBacked; + if (!isLayerBacked) { + // No need for the lock and call the view explicitly in case it needs to be loaded first + ASDN::MutexUnlocker u(__instanceLock__); + return self.view.layer; + } + + if (![self _locked_shouldLoadViewOrLayer]) { + return nil; + } + + // Loading a layer needs to happen on the main thread + ASDisplayNodeAssertMainThread(); + [self _locked_loadViewOrLayerIsLayerBacked:isLayerBacked]; + + // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout + // but automatic subnode management would require us to modify the node tree + // in the background on a loaded node, which isn't currently supported. + if (_pendingViewState.hasSetNeedsLayout) { + // Need to unlock before calling setNeedsLayout to avoid deadlocks. + // MutexUnlocker will re-lock at the end of scope. + ASDN::MutexUnlocker u(__instanceLock__); + [self __setNeedsLayout]; + } + + [self _locked_applyPendingStateToViewOrLayer]; + + { + // The following methods should not be called with a lock + ASDN::MutexUnlocker u(__instanceLock__); + + // No need for the lock as accessing the subviews or layers are always happening on main + [self _addSubnodeViewsAndLayers]; + + // A subclass hook should never be called with a lock + [self _didLoad]; } + + return _layer; +} + +// Returns nil if the layer is not an _ASDisplayLayer; will not create the layer if nil. +- (_ASDisplayLayer *)asyncLayer +{ + ASDN::MutexLocker l(__instanceLock__); + return [self _locked_asyncLayer]; +} + +- (_ASDisplayLayer *)_locked_asyncLayer +{ + return [_layer isKindOfClass:[_ASDisplayLayer class]] ? (_ASDisplayLayer *)_layer : nil; } - (BOOL)isSynchronous { + ASDN::MutexLocker l(__instanceLock__); return _flags.synchronous; } - (void)setSynchronous:(BOOL)flag { + ASDN::MutexLocker l(__instanceLock__); _flags.synchronous = flag; } - (void)setLayerBacked:(BOOL)isLayerBacked { - if (![self.class layerBackedNodesEnabled]) return; + if (![self.class layerBackedNodesEnabled]) { + return; + } ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssert(!_view && !_layer, @"Cannot change isLayerBacked after layer or view has loaded"); @@ -783,9 +833,47 @@ - (BOOL)isLayerBacked return _flags.layerBacked; } -#pragma mark - Style - -- (ASLayoutElementStyle *)style +- (BOOL)shouldAnimateSizeChanges +{ + ASDN::MutexLocker l(__instanceLock__); + return _flags.shouldAnimateSizeChanges; +} + +- (void)setShouldAnimateSizeChanges:(BOOL)shouldAnimateSizeChanges +{ + ASDN::MutexLocker l(__instanceLock__); + _flags.shouldAnimateSizeChanges = shouldAnimateSizeChanges; +} + +- (CGRect)threadSafeBounds +{ + ASDN::MutexLocker l(__instanceLock__); + return _threadSafeBounds; +} + +- (void)setThreadSafeBounds:(CGRect)newBounds +{ + ASDN::MutexLocker l(__instanceLock__); + _threadSafeBounds = newBounds; +} + +#pragma mark - Layout + +#if DEBUG + #define AS_DEDUPE_LAYOUT_SPEC_TREE 1 +#endif + +// At most a layoutSpecBlock or one of the three layout methods is overridden +#define __ASDisplayNodeCheckForLayoutMethodOverrides \ + ASDisplayNodeAssert(_layoutSpecBlock != NULL || \ + ((ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateSizeThatFits:)) ? 1 : 0) \ + + (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(layoutSpecThatFits:)) ? 1 : 0) \ + + (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateLayoutThatFits:)) ? 1 : 0)) <= 1, \ + @"Subclass %@ must at least provide a layoutSpecBlock or override at most one of the three layout methods: calculateLayoutThatFits:, layoutSpecThatFits:, or calculateSizeThatFits:", NSStringFromClass(self.class)) + +#pragma mark + +- (ASLayoutElementStyle *)style { ASDN::MutexLocker l(__instanceLock__); if (_style == nil) { @@ -794,82 +882,50 @@ - (ASLayoutElementStyle *)style return _style; } -- (instancetype)styledWithBlock:(void (^)(ASLayoutElementStyle *style))styleBlock +- (ASLayoutElementType)layoutElementType { - styleBlock(self.style); - return self; + return ASLayoutElementTypeDisplayNode; } -#pragma mark - Layout +- (BOOL)canLayoutAsynchronous +{ + return !self.isNodeLoaded; +} -- (void)setNeedsLayoutFromAbove +- (NSArray> *)sublayoutElements { - ASDisplayNodeAssertThreadAffinity(self); - - __instanceLock__.lock(); + return self.subnodes; +} - // Mark the node for layout in the next layout pass - [self setNeedsLayout]; - - // Escalate to the root; entire tree must allow adjustments so the layout fits the new child. - // Much of the layout will be re-used as cached (e.g. other items in an unconstrained stack) - ASDisplayNode *supernode = _supernode; - if (supernode) { - // Threading model requires that we unlock before calling a method on our parent. - __instanceLock__.unlock(); - [supernode setNeedsLayoutFromAbove]; - return; - } - - // We are the root node and need to re-flow the layout; at least one child needs a new size. - CGSize boundsSizeForLayout = ASCeilSizeValues(self.bounds.size); +- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock +{ + styleBlock(self.style); + return self; +} - // Figure out constrainedSize to use - ASSizeRange constrainedSize = ASSizeRangeMake(boundsSizeForLayout); - if (_pendingDisplayNodeLayout != nullptr) { - constrainedSize = _pendingDisplayNodeLayout->constrainedSize; - } else if (_calculatedDisplayNodeLayout->layout != nil) { - constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; - } +ASLayoutElementFinalLayoutElementDefault - // Perform a measurement pass to get the full tree layout, adapting to the child's new size. - ASLayout *layout = [self layoutThatFits:constrainedSize]; - - // Check if the returned layout has a different size than our current bounds. - if (CGSizeEqualToSize(boundsSizeForLayout, layout.size) == NO) { - // If so, inform our container we need an update (e.g Table, Collection, ViewController, etc). - [self _locked_displayNodeDidInvalidateSizeNewSize:layout.size]; - } - - __instanceLock__.unlock(); +- (NSString *)debugName +{ + ASDN::MutexLocker l(__instanceLock__); + return _debugName; } -- (void)_locked_displayNodeDidInvalidateSizeNewSize:(CGSize)size +- (void)setDebugName:(NSString *)debugName { - ASDisplayNodeAssertThreadAffinity(self); - - // The default implementation of display node changes the size of itself to the new size - CGRect oldBounds = self.bounds; - CGSize oldSize = oldBounds.size; - CGSize newSize = size; - - if (! CGSizeEqualToSize(oldSize, newSize)) { - self.bounds = (CGRect){ oldBounds.origin, newSize }; - - // Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint - // and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted. - CGPoint anchorPoint = self.anchorPoint; - CGPoint oldPosition = self.position; - CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x; - CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; - self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); + ASDN::MutexLocker l(__instanceLock__); + if (!ASObjectIsEqual(_debugName, debugName)) { + _debugName = [debugName copy]; } } +#pragma mark Measurement Pass + - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" + // For now we just call the deprecated measureWithSizeRange: method to not break old API return [self measureWithSizeRange:constrainedSize]; #pragma clang diagnostic pop } @@ -878,18 +934,22 @@ - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)par { ASDN::MutexLocker l(__instanceLock__); - // If multiple layout transitions are in progress it can happen that an invalid one is still trying to do a measurement - // before it get's cancelled. In this case we should not touch any layout and return a no op layout + // If one or multiple layout transitions are in flight it still can happen that layout information is requested + // on other threads. As the pending and calculated layout to be updated in the layout transition in here just a + // layout calculation wil be performed without side effect if ([self _isLayoutTransitionInvalid]) { - return [ASLayout layoutWithLayoutElement:self size:{0, 0}]; + return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize]; } - + if (_calculatedDisplayNodeLayout->isValidForConstrainedSizeParentSize(constrainedSize, parentSize)) { ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _calculatedDisplayNodeLayout->layout should not be nil! %@", self); + // Our calculated layout is suitable for this constrainedSize, so keep using it and + // invalidate any pending layout that has been generated in the past. + _pendingDisplayNodeLayout = nullptr; return _calculatedDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}]; } - // Creat a pending display node layout for the layout pass + // Create a pending display node layout for the layout pass _pendingDisplayNodeLayout = std::make_shared( [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], constrainedSize, @@ -900,1514 +960,1671 @@ - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)par return _pendingDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}]; } -- (ASLayoutElementType)layoutElementType -{ - return ASLayoutElementTypeDisplayNode; -} +#pragma mark Layout Pass -- (BOOL)canLayoutAsynchronous +- (void)__setNeedsLayout { - return !self.isNodeLoaded; + [self invalidateCalculatedLayout]; } -#pragma mark - Automatic Hierarchy - -- (BOOL)automaticallyManagesSubnodes +- (void)invalidateCalculatedLayout { ASDN::MutexLocker l(__instanceLock__); - return _automaticallyManagesSubnodes; -} + + // This will cause the next layout pass to compute a new layout instead of returning + // the cached layout in case the constrained or parent size did not change + _calculatedDisplayNodeLayout->invalidate(); + if (_pendingDisplayNodeLayout != nullptr) { + _pendingDisplayNodeLayout->invalidate(); + } -- (void)setAutomaticallyManagesSubnodes:(BOOL)automaticallyManagesSubnodes -{ - ASDN::MutexLocker l(__instanceLock__); - _automaticallyManagesSubnodes = automaticallyManagesSubnodes; +#if YOGA + [self invalidateCalculatedYogaLayout]; +#endif } -#pragma mark - Layout Transition - -- (void)transitionLayoutWithAnimation:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion +- (void)__layout { - ASDisplayNodeAssertMainThread(); - - [self setNeedsLayout]; - [self.view layoutIfNeeded]; + ASDisplayNodeAssertThreadAffinity(self); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + + { + ASDN::MutexLocker l(__instanceLock__); + CGRect bounds = _threadSafeBounds; - [self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass] - animated:animated - shouldMeasureAsync:shouldMeasureAsync - measurementCompletion:completion]; + 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. + LOG(@"Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); + return; + } + + // If a current layout transition is in progress there is no need to do a measurement and layout pass in here as + // this is supposed to happen within the layout transition process + if (_transitionInProgress) { + return; + } + + // This method will confirm that the layout is up to date (and update if needed). + // Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning). + [self _locked_measureNodeWithBoundsIfNecessary:bounds]; + _pendingDisplayNodeLayout = nullptr; + + [self _locked_layoutPlaceholderIfNecessary]; + } + [self _layoutSublayouts]; + + ASPerformBlockOnMainThread(^{ + [self layout]; + [self layoutDidFinish]; + }); } -- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize - animated:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion +/// Needs to be called with lock held +- (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds { - ASDisplayNodeAssertMainThread(); - - if (constrainedSize.max.width <= 0.0 || constrainedSize.max.height <= 0.0) { - // Using CGSizeZero for the sizeRange can cause negative values in client layout code. - // Most likely called transitionLayout: without providing a size, before first layout pass. - return; - } - // Check if we are a subnode in a layout transition. - // In this case no measurement is needed as we're part of the layout transition. + // In this case no measurement is needed as it's part of the layout transition if ([self _isLayoutTransitionInvalid]) { return; } - { - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one."); + CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size); + + // Prefer _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout (if exists, it's the newest) + // If there is no _pending, check if _calculated is valid to reuse (avoiding recalculation below). + if (_pendingDisplayNodeLayout == nullptr) { + if (_calculatedDisplayNodeLayout->isDirty() == NO + && (_calculatedDisplayNodeLayout->requestedLayoutFromAbove == YES + || CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) { + return; + } } - // Every new layout transition has a transition id associated to check in subsequent transitions for cancelling - int32_t transitionID = [self _startNewTransition]; + // _calculatedDisplayNodeLayout is not reusable we need to transition to a new one + [self cancelLayoutTransition]; - // Move all subnodes in layout pending state for this transition - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - ASDisplayNodeAssert([node _isTransitionInProgress] == NO, @"Can't start a transition when one of the subnodes is performing one."); - node.hierarchyState |= ASHierarchyStateLayoutPending; - node.pendingTransitionID = transitionID; - }); + BOOL didCreateNewContext = NO; + ASLayoutElementContext context = ASLayoutElementGetCurrentContext(); + if (ASLayoutElementContextIsNull(context)) { + context = ASLayoutElementContextMake(ASLayoutElementContextDefaultTransitionID); + ASLayoutElementSetCurrentContext(context); + didCreateNewContext = YES; + } - // Transition block that executes the layout transition - void (^transitionBlock)(void) = ^{ - if ([self _shouldAbortTransitionWithID:transitionID]) { - return; - } + // Figure out previous and pending layouts for layout transition + std::shared_ptr nextLayout = _pendingDisplayNodeLayout; + #define layoutSizeDifferentFromBounds !CGSizeEqualToSize(nextLayout->layout.size, boundsSizeForLayout) + + // nextLayout was likely created by a call to layoutThatFits:, check if it is valid and can be applied. + // If our bounds size is different than it, or invalid, recalculate. Use #define to avoid nullptr-> + if (nextLayout == nullptr || nextLayout->isDirty() == YES || layoutSizeDifferentFromBounds) { + // Use the last known constrainedSize passed from a parent during layout (if never, use bounds). + ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass]; + ASLayout *layout = [self calculateLayoutThatFits:constrainedSize + restrictedToSize:self.style.size + relativeToParentSize:boundsSizeForLayout]; - // Perform a full layout creation pass with passed in constrained size to create the new layout for the transition - ASLayout *newLayout; - { - ASDN::MutexLocker l(__instanceLock__); + nextLayout = std::make_shared(layout, constrainedSize, boundsSizeForLayout); + } + + if (didCreateNewContext) { + ASLayoutElementClearCurrentContext(); + } + + // If our new layout's desired size for self doesn't match current size, ask our parent to update it. + // This can occur for either pre-calculated or newly-calculated layouts. + if (nextLayout->requestedLayoutFromAbove == NO + && CGSizeEqualToSize(boundsSizeForLayout, nextLayout->layout.size) == NO) { + // The layout that we have specifies that this node (self) would like to be a different size + // than it currently is. Because that size has been computed within the constrainedSize, we + // expect that calling setNeedsLayoutFromAbove will result in our parent resizing us to this. + // However, in some cases apps may manually interfere with this (setting a different bounds). + // In this case, we need to detect that we've already asked to be resized to match this + // particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout. + nextLayout->requestedLayoutFromAbove = YES; + [self _setNeedsLayoutFromAbove]; + } - BOOL shouldVisualizeLayout = ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); - ASLayoutElementSetCurrentContext(ASLayoutElementContextMake(transitionID, shouldVisualizeLayout)); + // Prepare to transition to nextLayout + ASDisplayNodeAssertNotNil(nextLayout->layout, @"nextLayout->layout should not be nil! %@", self); + _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self + pendingLayout:nextLayout + previousLayout:_calculatedDisplayNodeLayout]; - BOOL automaticallyManagesSubnodesDisabled = (self.automaticallyManagesSubnodes == NO); - self.automaticallyManagesSubnodes = YES; // Temporary flag for 1.9.x - newLayout = [self calculateLayoutThatFits:constrainedSize - restrictedToSize:self.style.size - relativeToParentSize:constrainedSize.max]; - if (automaticallyManagesSubnodesDisabled) { - self.automaticallyManagesSubnodes = NO; // Temporary flag for 1.9.x - } - - ASLayoutElementClearCurrentContext(); - } - - if ([self _shouldAbortTransitionWithID:transitionID]) { - return; - } - - ASPerformBlockOnMainThread(^{ - // Grab __instanceLock__ here to make sure this transition isn't invalidated - // right after it passed the validation test and before it proceeds - ASDN::MutexLocker l(__instanceLock__); - - if ([self _shouldAbortTransitionWithID:transitionID]) { - return; - } + // If a parent is currently executing a layout transition, perform our layout application after it. + if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) { + // If no transition, apply our new layout immediately (common case). + [self _completePendingLayoutTransition]; + } +} - // Update calculated layout - auto previousLayout = _calculatedDisplayNodeLayout; - auto pendingLayout = std::make_shared( - newLayout, - constrainedSize, - constrainedSize.max - ); - [self setCalculatedDisplayNodeLayout:pendingLayout]; - - // Apply complete layout transitions for all subnodes - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - [node _completePendingLayoutTransition]; - node.hierarchyState &= (~ASHierarchyStateLayoutPending); - }); - - [self _finishOrCancelTransition]; - - // Measurement pass completion - if (completion) { - completion(); - } - - // Setup pending layout transition for animation - _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self - pendingLayout:pendingLayout - previousLayout:previousLayout]; - // Setup context for pending layout transition. we need to hold a strong reference to the context - _pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated - layoutDelegate:_pendingLayoutTransition - completionDelegate:self]; - - // Apply the subnode insertion immediately to be able to animate the nodes - [_pendingLayoutTransition applySubnodeInsertions]; - - // Kick off animating the layout transition - [self animateLayoutTransition:_pendingLayoutTransitionContext]; - }); - }; +- (ASSizeRange)_locked_constrainedSizeForLayoutPass +{ + // TODO: The logic in -_setNeedsLayoutFromAbove seems correct and doesn't use this method. + // logic seems correct. For what case does -this method need to do the CGSizeEqual checks? + // IF WE CAN REMOVE BOUNDS CHECKS HERE, THEN WE CAN ALSO REMOVE "REQUESTED FROM ABOVE" CHECK - // Start transition based on flag on current or background thread - if (shouldMeasureAsync) { - ASPerformBlockOnBackgroundThread(transitionBlock); + CGSize boundsSizeForLayout = ASCeilSizeValues(self.threadSafeBounds.size); + + // Checkout if constrained size of pending or calculated display node layout can be used + if (_pendingDisplayNodeLayout != nullptr + && (_pendingDisplayNodeLayout->requestedLayoutFromAbove + || CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, boundsSizeForLayout))) { + // We assume the size from the last returned layoutThatFits: layout was applied so use the pending display node + // layout constrained size + return _pendingDisplayNodeLayout->constrainedSize; + } else if (_calculatedDisplayNodeLayout->layout != nil + && (_calculatedDisplayNodeLayout->requestedLayoutFromAbove + || CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) { + // We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different + return _calculatedDisplayNodeLayout->constrainedSize; } else { - transitionBlock(); + // In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can + // be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to + // the one returned from layoutThatFits: or layoutThatFits: was never called + return ASSizeRangeMake(boundsSizeForLayout); } } -- (void)cancelLayoutTransition +- (void)layoutDidFinish { - ASDN::MutexLocker l(__instanceLock__); - if ([self _isTransitionInProgress]) { - // Cancel transition in progress - [self _finishOrCancelTransition]; - - // Tell subnodes to exit layout pending state and clear related properties - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - node.hierarchyState &= (~ASHierarchyStateLayoutPending); - }); - } + // Hook for subclasses + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); } -- (BOOL)_isTransitionInProgress +#pragma mark Calculation + +- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize + restrictedToSize:(ASLayoutElementSize)size + relativeToParentSize:(CGSize)parentSize { - ASDN::MutexLocker l(__instanceLock__); - return _transitionInProgress; + ASSizeRange styleAndParentSize = ASLayoutElementSizeResolve(self.style.size, parentSize); + const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, styleAndParentSize); + return [self calculateLayoutThatFits:resolvedRange]; } -- (BOOL)_isLayoutTransitionInvalid +- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { + __ASDisplayNodeCheckForLayoutMethodOverrides; + ASDN::MutexLocker l(__instanceLock__); - if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { - ASLayoutElementContext context = ASLayoutElementGetCurrentContext(); - if (ASLayoutElementContextIsNull(context) || _pendingTransitionID != context.transitionID) { - return YES; + +#if YOGA /* YOGA */ + if (ASHierarchyStateIncludesYogaLayoutEnabled(_hierarchyState) == YES && + ASHierarchyStateIncludesYogaLayoutMeasuring(_hierarchyState) == NO) { + ASDN::MutexUnlocker ul(__instanceLock__); + [self calculateLayoutFromYogaRoot:constrainedSize]; + } + + if (ASHierarchyStateIncludesYogaLayoutEnabled(_hierarchyState) == YES && self.yogaCalculatedLayout) { + return self.yogaCalculatedLayout; + } +#endif /* YOGA */ + + // Manual size calculation via calculateSizeThatFits: + if (((_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) || + (_layoutSpecBlock != NULL)) == NO) { + CGSize size = [self calculateSizeThatFits:constrainedSize.max]; + ASDisplayNodeLogEvent(self, @"calculatedSize: %@", NSStringFromCGSize(size)); + return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil]; + } + + // Size calcualtion with layout elements + BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; + if (measureLayoutSpec) { + _layoutSpecNumberOfPasses++; + } + + // Get layout element from the node + id layoutElement = [self _locked_layoutElementThatFits:constrainedSize]; + + // Certain properties are necessary to set on an element of type ASLayoutSpec + if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) { + ASLayoutSpec *layoutSpec = (ASLayoutSpec *)layoutElement; + +#if AS_DEDUPE_LAYOUT_SPEC_TREE + NSSet *duplicateElements = [layoutSpec findDuplicatedElementsInSubtree]; + if (duplicateElements.count > 0) { + ASDisplayNodeFailAssert(@"Node %@ returned a layout spec that contains the same elements in multiple positions. Elements: %@", self, duplicateElements); + // Use an empty layout spec to avoid crashes + layoutSpec = [[ASLayoutSpec alloc] init]; } +#endif + + ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec); + + layoutSpec.isMutable = NO; } - return NO; + + // Manually propagate the trait collection here so that any layoutSpec children of layoutSpec will get a traitCollection + { + + ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); + ASTraitCollectionPropagateDown(layoutElement, self.primitiveTraitCollection); + } + + BOOL measureLayoutComputation = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation; + if (measureLayoutComputation) { + _layoutComputationNumberOfPasses++; + } + + // Layout element layout creation + ASLayout *layout = ({ + ASDN::SumScopeTimer t(_layoutComputationTotalTime, measureLayoutComputation); + [layoutElement layoutThatFits:constrainedSize]; + }); + ASDisplayNodeAssertNotNil(layout, @"[ASLayoutElement layoutThatFits:] should never return nil! %@, %@", self, layout); + + // Make sure layoutElementObject of the root layout is `self`, so that the flattened layout will be structurally correct. + BOOL isFinalLayoutElement = (layout.layoutElement != self); + if (isFinalLayoutElement) { + layout.position = CGPointZero; + layout = [ASLayout layoutWithLayoutElement:self size:layout.size sublayouts:@[layout]]; + } + ASDisplayNodeLogEvent(self, @"computedLayout: %@", layout); + + return [layout filteredNodeLayoutTree]; } -/// Starts a new transition and returns the transition id -- (int32_t)_startNewTransition +- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - ASDN::MutexLocker l(__instanceLock__); - _transitionInProgress = YES; - _transitionID = OSAtomicAdd32(1, &_transitionID); - return _transitionID; + __ASDisplayNodeCheckForLayoutMethodOverrides; + +#if ASDISPLAYNODE_ASSERTIONS_ENABLED + if (ASIsCGSizeValidForSize(constrainedSize) == NO) { + NSLog(@"Cannot calculate size of node: constrainedSize is infinite and node does not override -calculateSizeThatFits: or specify a preferredSize. Try setting style.preferredSize. Node: %@", [self displayNodeRecursiveDescription]); + } +#endif + + return ASIsCGSizeValidForSize(constrainedSize) ? constrainedSize : CGSizeZero; } -- (void)_finishOrCancelTransition +- (id)_locked_layoutElementThatFits:(ASSizeRange)constrainedSize { - ASDN::MutexLocker l(__instanceLock__); - _transitionInProgress = NO; + __ASDisplayNodeCheckForLayoutMethodOverrides; + + BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; + + if (_layoutSpecBlock != NULL) { + return ({ + ASDN::MutexLocker l(__instanceLock__); + ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); + _layoutSpecBlock(self, constrainedSize); + }); + } else { + return ({ + ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); + [self layoutSpecThatFits:constrainedSize]; + }); + } } -- (BOOL)_shouldAbortTransitionWithID:(int32_t)transitionID +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { - ASDN::MutexLocker l(__instanceLock__); - return (!_transitionInProgress || _transitionID != transitionID); + __ASDisplayNodeCheckForLayoutMethodOverrides; + + ASDisplayNodeAssert(NO, @"-[ASDisplayNode layoutSpecThatFits:] should never return an empty value. One way this is caused is by calling -[super layoutSpecThatFits:] which is not currently supported."); + return [[ASLayoutSpec alloc] init]; } -#pragma mark Layout Transition API - -- (void)setDefaultLayoutTransitionDuration:(NSTimeInterval)defaultLayoutTransitionDuration +- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock { + // For now there should never be an override of layoutSpecThatFits: / layoutElementThatFits: and a layoutSpecBlock + ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits), @"Overwriting layoutSpecThatFits: and providing a layoutSpecBlock block is currently not supported"); + ASDN::MutexLocker l(__instanceLock__); - _defaultLayoutTransitionDuration = defaultLayoutTransitionDuration; + _layoutSpecBlock = layoutSpecBlock; } -- (NSTimeInterval)defaultLayoutTransitionDuration +- (ASLayoutSpecBlock)layoutSpecBlock { ASDN::MutexLocker l(__instanceLock__); - return _defaultLayoutTransitionDuration; + return _layoutSpecBlock; } -- (void)setDefaultLayoutTransitionDelay:(NSTimeInterval)defaultLayoutTransitionDelay +- (ASLayout *)calculatedLayout { ASDN::MutexLocker l(__instanceLock__); - _defaultLayoutTransitionDelay = defaultLayoutTransitionDelay; + return _calculatedDisplayNodeLayout->layout; } -- (NSTimeInterval)defaultLayoutTransitionDelay +- (void)_setCalculatedDisplayNodeLayout:(std::shared_ptr)displayNodeLayout { ASDN::MutexLocker l(__instanceLock__); - return _defaultLayoutTransitionDelay; + [self _locked_setCalculatedDisplayNodeLayout:displayNodeLayout]; } -- (void)setDefaultLayoutTransitionOptions:(UIViewAnimationOptions)defaultLayoutTransitionOptions +- (void)_locked_setCalculatedDisplayNodeLayout:(std::shared_ptr)displayNodeLayout +{ + ASDisplayNodeAssertTrue(displayNodeLayout->layout.layoutElement == self); + ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.width >= 0.0); + ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.height >= 0.0); + + _calculatedDisplayNodeLayout = displayNodeLayout; +} + + +- (CGSize)calculatedSize { ASDN::MutexLocker l(__instanceLock__); - _defaultLayoutTransitionOptions = defaultLayoutTransitionOptions; + if (_pendingDisplayNodeLayout != nullptr) { + return _pendingDisplayNodeLayout->layout.size; + } + return _calculatedDisplayNodeLayout->layout.size; } -- (UIViewAnimationOptions)defaultLayoutTransitionOptions +- (ASSizeRange)constrainedSizeForCalculatedLayout { ASDN::MutexLocker l(__instanceLock__); - return _defaultLayoutTransitionOptions; + if (_pendingDisplayNodeLayout != nullptr) { + return _pendingDisplayNodeLayout->constrainedSize; + } + return _calculatedDisplayNodeLayout->constrainedSize; } -/* - * Hook for subclasses to perform an animation based on the given ASContextTransitioning. By default a fade in and out - * animation is provided. +/** + * @abstract Informs the root node that the intrinsic size of the receiver is no longer valid. + * + * @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know + * that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen. */ -- (void)animateLayoutTransition:(id)context +- (void)_setNeedsLayoutFromAbove { - if ([context isAnimated] == NO) { - [self __layoutSublayouts]; - [context completeTransition:YES]; - return; - } - - ASDisplayNode *node = self; - - NSAssert(node.isNodeLoaded == YES, @"Invalid node state"); - - NSArray *removedSubnodes = [context removedSubnodes]; - NSMutableArray *removedViews = [NSMutableArray array]; - NSMutableArray *insertedSubnodes = [[context insertedSubnodes] mutableCopy]; - NSMutableArray *movedSubnodes = [NSMutableArray array]; + ASDisplayNodeAssertThreadAffinity(self); + + // Mark the node for layout in the next layout pass + [self setNeedsLayout]; - for (ASDisplayNode *subnode in [context subnodesForKey:ASTransitionContextToLayoutKey]) { - if ([insertedSubnodes containsObject:subnode] == NO) { - // This is an existing subnode, check if it is resized, moved or both - CGRect fromFrame = [context initialFrameForNode:subnode]; - CGRect toFrame = [context finalFrameForNode:subnode]; - if (CGSizeEqualToSize(fromFrame.size, toFrame.size) == NO) { - // To crossfade resized subnodes, show a snapshot of it on top. - // The node itself can then be treated as a newly-inserted one. - UIView *snapshotView = [subnode.view snapshotViewAfterScreenUpdates:YES]; - snapshotView.frame = [context initialFrameForNode:subnode]; - snapshotView.alpha = 1; - - [node.view insertSubview:snapshotView aboveSubview:subnode.view]; - [removedViews addObject:snapshotView]; - - [insertedSubnodes addObject:subnode]; - } - if (CGPointEqualToPoint(fromFrame.origin, toFrame.origin) == NO) { - [movedSubnodes addObject:subnode]; - } - } - } + __instanceLock__.lock(); + // Escalate to the root; entire tree must allow adjustments so the layout fits the new child. + // Much of the layout will be re-used as cached (e.g. other items in an unconstrained stack) + ASDisplayNode *supernode = _supernode; + __instanceLock__.unlock(); - for (ASDisplayNode *insertedSubnode in insertedSubnodes) { - insertedSubnode.frame = [context finalFrameForNode:insertedSubnode]; - insertedSubnode.alpha = 0; + if (supernode) { + // Threading model requires that we unlock before calling a method on our parent. + [supernode _setNeedsLayoutFromAbove]; + } else { + // Let the root node method know that the size was invalidated + [self _rootNodeDidInvalidateSize]; } - - [UIView animateWithDuration:self.defaultLayoutTransitionDuration delay:self.defaultLayoutTransitionDelay options:self.defaultLayoutTransitionOptions animations:^{ - // Fade removed subnodes and views out - for (ASDisplayNode *removedSubnode in removedSubnodes) { - removedSubnode.alpha = 0; - } - for (UIView *removedView in removedViews) { - removedView.alpha = 0; - } - - // Fade inserted subnodes in - for (ASDisplayNode *insertedSubnode in insertedSubnodes) { - insertedSubnode.alpha = 1; - } - - // Update frame of self and moved subnodes - CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size; - CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size; - BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO); - if (isResized == YES) { - CGPoint position = node.frame.origin; - node.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height); - } - for (ASDisplayNode *movedSubnode in movedSubnodes) { - movedSubnode.frame = [context finalFrameForNode:movedSubnode]; - } - } completion:^(BOOL finished) { - for (UIView *removedView in removedViews) { - [removedView removeFromSuperview]; - } - // Subnode removals are automatically performed - [context completeTransition:finished]; - }]; } -/* - * Hook for subclasses to clean up nodes after the transition happened. Furthermore this can be used from subclasses - * to manually perform deletions. - */ -- (void)didCompleteLayoutTransition:(id)context +- (void)_rootNodeDidInvalidateSize { - [_pendingLayoutTransition applySubnodeRemovals]; -} + ASDisplayNodeAssertThreadAffinity(self); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + + __instanceLock__.lock(); + + // We are the root node and need to re-flow the layout; at least one child needs a new size. + CGSize boundsSizeForLayout = ASCeilSizeValues(self.bounds.size); -#pragma mark _ASTransitionContextCompletionDelegate + // Figure out constrainedSize to use + ASSizeRange constrainedSize = ASSizeRangeMake(boundsSizeForLayout); + if (_pendingDisplayNodeLayout != nullptr) { + constrainedSize = _pendingDisplayNodeLayout->constrainedSize; + } else if (_calculatedDisplayNodeLayout->layout != nil) { + constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; + } -/* - * After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this - * delegate method will be called that start the completion process of the - */ -- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete -{ - [self didCompleteLayoutTransition:context]; - _pendingLayoutTransitionContext = nil; + __instanceLock__.unlock(); - [self _pendingLayoutTransitionDidComplete]; + // Perform a measurement pass to get the full tree layout, adapting to the child's new size. + ASLayout *layout = [self layoutThatFits:constrainedSize]; + + // Check if the returned layout has a different size than our current bounds. + if (CGSizeEqualToSize(boundsSizeForLayout, layout.size) == NO) { + // If so, inform our container we need an update (e.g Table, Collection, ViewController, etc). + [self displayNodeDidInvalidateSizeNewSize:layout.size]; + } } -#pragma mark - Layout - -/* - * Completes the pending layout transition immediately without going through the the Layout Transition Animation API - */ -- (void)_completePendingLayoutTransition +- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)size { - ASDN::MutexLocker l(__instanceLock__); - if (_pendingLayoutTransition) { - [self setCalculatedDisplayNodeLayout:_pendingLayoutTransition.pendingLayout]; - [self _completeLayoutTransition:_pendingLayoutTransition]; + ASDisplayNodeAssertThreadAffinity(self); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + + // The default implementation of display node changes the size of itself to the new size + CGRect oldBounds = self.bounds; + CGSize oldSize = oldBounds.size; + CGSize newSize = size; + + if (! CGSizeEqualToSize(oldSize, newSize)) { + self.bounds = (CGRect){ oldBounds.origin, newSize }; + + // Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint + // and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted. + CGPoint anchorPoint = self.anchorPoint; + CGPoint oldPosition = self.position; + CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x; + CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; + self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); } - [self _pendingLayoutTransitionDidComplete]; } -/* - * Can be directly called to commit the given layout transition immediately to complete without calling through to the - * Layout Transition Animation API - */ -- (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition +- (void)layout { - // Layout transition is not supported for nodes that are not have automatic subnode management enabled - if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) { - return; - } - - // Trampoline to the main thread if necessary - if (ASDisplayNodeThreadIsMain() || layoutTransition.isSynchronous == NO) { - [layoutTransition commitTransition]; - } else { - // Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded - ASPerformBlockOnMainThread(^{ - [layoutTransition commitTransition]; - }); - } + ASDisplayNodeAssertMainThread(); + // Subclass hook } -- (void)_pendingLayoutTransitionDidComplete +- (void)_layoutSublayouts { - ASDN::MutexLocker l(__instanceLock__); + ASDisplayNodeAssertThreadAffinity(self); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); - // Subclass hook - [self calculatedLayoutDidChange]; - - // 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]) { - - // Zero-sized nodes do not require a placeholder. - ASLayout *layout = _calculatedDisplayNodeLayout->layout; - CGSize layoutSize = (layout ? layout.size : CGSizeZero); - if (CGSizeEqualToSize(layoutSize, CGSizeZero)) { + ASLayout *layout; + NSArray *subnodes; + { + ASDN::MutexLocker l(__instanceLock__); + if (_calculatedDisplayNodeLayout->isDirty() || _subnodes.count == 0) { return; } - - if (!_placeholderImage) { - ASPerformBlockOnMainThread(^{ - if (self.contents == nil) { - _placeholderImage = [self placeholderImage]; - } - }); - } + layout = _calculatedDisplayNodeLayout->layout; + subnodes = [_subnodes copy]; } - // Cleanup pending layout transition - _pendingLayoutTransition = nil; -} - -- (void)calculatedLayoutDidChange -{ - // subclass override + for (ASDisplayNode *node in subnodes) { + CGRect frame = [layout frameForElement:node]; + if (CGRectIsNull(frame)) { + // There is no frame for this node in our layout. + // This currently can happen if we get a CA layout pass + // while waiting for the client to run animateLayoutTransition: + } else { + node.frame = frame; + } + } } -- (void)setMeasurementOptions:(ASDisplayNodePerformanceMeasurementOptions)measurementOptions -{ - ASDN::MutexLocker l(__instanceLock__); - _measurementOptions = measurementOptions; -} +#pragma mark Automatically Manages Subnodes -- (ASDisplayNodePerformanceMeasurementOptions)measurementOptions +- (BOOL)automaticallyManagesSubnodes { ASDN::MutexLocker l(__instanceLock__); - return _measurementOptions; + return _automaticallyManagesSubnodes; } -- (ASDisplayNodePerformanceMeasurements)performanceMeasurements +- (void)setAutomaticallyManagesSubnodes:(BOOL)automaticallyManagesSubnodes { ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodePerformanceMeasurements measurements = { .layoutSpecNumberOfPasses = -1, .layoutSpecTotalTime = NAN, .layoutComputationNumberOfPasses = -1, .layoutComputationTotalTime = NAN }; - if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec) { - measurements.layoutSpecNumberOfPasses = _layoutSpecNumberOfPasses; - measurements.layoutSpecTotalTime = _layoutSpecTotalTime; - } - if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation) { - measurements.layoutComputationNumberOfPasses = _layoutComputationNumberOfPasses; - measurements.layoutComputationTotalTime = _layoutComputationTotalTime; - } - return measurements; + _automaticallyManagesSubnodes = automaticallyManagesSubnodes; } -#pragma mark - Asynchronous display +#pragma mark Layout Transition -- (BOOL)displaysAsynchronously +- (void)transitionLayoutWithAnimation:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(void(^)())completion { - ASDN::MutexLocker l(__instanceLock__); - return [self _displaysAsynchronously]; -} + ASDisplayNodeAssertMainThread(); -/** - * Core implementation of -displaysAsynchronously. - * Must be called with __instanceLock__ held. - */ -- (BOOL)_displaysAsynchronously -{ - return _flags.synchronous == NO && _flags.displaysAsynchronously; + [self setNeedsLayout]; + + [self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass] + animated:animated + shouldMeasureAsync:shouldMeasureAsync + measurementCompletion:completion]; + } -- (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously +- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize + animated:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(void(^)())completion { - ASDisplayNodeAssertThreadAffinity(self); - - // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel) - if (_flags.synchronous) + ASDisplayNodeAssertMainThread(); + + if (constrainedSize.max.width <= 0.0 || constrainedSize.max.height <= 0.0) { + // Using CGSizeZero for the sizeRange can cause negative values in client layout code. + // Most likely called transitionLayout: without providing a size, before first layout pass. return; - - ASDN::MutexLocker l(__instanceLock__); - - if (_flags.displaysAsynchronously == displaysAsynchronously) + } + + // Check if we are a subnode in a layout transition. + // In this case no measurement is needed as we're part of the layout transition. + if ([self _isLayoutTransitionInvalid]) { return; - - _flags.displaysAsynchronously = displaysAsynchronously; - - self.asyncLayer.displaysAsynchronously = displaysAsynchronously; -} - -- (BOOL)shouldRasterizeDescendants -{ - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodeAssert(!((_hierarchyState & ASHierarchyStateRasterized) && _flags.shouldRasterizeDescendants), - @"Subnode of a rasterized node should not have redundant shouldRasterizeDescendants enabled"); - return _flags.shouldRasterizeDescendants; -} - -- (void)setShouldRasterizeDescendants:(BOOL)shouldRasterize -{ - ASDisplayNodeAssertThreadAffinity(self); + } + { ASDN::MutexLocker l(__instanceLock__); - - if (_flags.shouldRasterizeDescendants == shouldRasterize) - return; - - _flags.shouldRasterizeDescendants = shouldRasterize; + ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one."); } - if (self.isNodeLoaded) { - // Recursively tear down or build up subnodes. - // TODO: When disabling rasterization, preserve rasterized backing store as placeholderImage - // while the newly materialized subtree finishes rendering. Then destroy placeholderImage to save memory. - [self recursivelyClearContents]; + // Every new layout transition has a transition id associated to check in subsequent transitions for cancelling + int32_t transitionID = [self _startNewTransition]; + + // Move all subnodes in layout pending state for this transition + ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { + ASDisplayNodeAssert([node _isTransitionInProgress] == NO, @"Can't start a transition when one of the subnodes is performing one."); + node.hierarchyState |= ASHierarchyStateLayoutPending; + node.pendingTransitionID = transitionID; + }); + + // Transition block that executes the layout transition + void (^transitionBlock)(void) = ^{ + if ([self _shouldAbortTransitionWithID:transitionID]) { + return; + } - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode *node) { - if (shouldRasterize) { - [node enterHierarchyState:ASHierarchyStateRasterized]; - [node __unloadNode]; - } else { - [node exitHierarchyState:ASHierarchyStateRasterized]; - [node __loadNode]; + // Perform a full layout creation pass with passed in constrained size to create the new layout for the transition + ASLayout *newLayout; + { + ASDN::MutexLocker l(__instanceLock__); + + ASLayoutElementSetCurrentContext(ASLayoutElementContextMake(transitionID)); + + BOOL automaticallyManagesSubnodesDisabled = (self.automaticallyManagesSubnodes == NO); + self.automaticallyManagesSubnodes = YES; // Temporary flag for 1.9.x + newLayout = [self calculateLayoutThatFits:constrainedSize + restrictedToSize:self.style.size + relativeToParentSize:constrainedSize.max]; + if (automaticallyManagesSubnodesDisabled) { + self.automaticallyManagesSubnodes = NO; // Temporary flag for 1.9.x } - }); - if (!shouldRasterize) { - // At this point all of our subnodes have their layers or views recreated, but we haven't added - // them to ours yet. This is because our node is already loaded, and the above recursion - // is only performed on our subnodes -- not self. - [self _addSubnodeViewsAndLayers]; + + ASLayoutElementClearCurrentContext(); } - if (ASInterfaceStateIncludesVisible(self.interfaceState)) { - // TODO: Change this to recursivelyEnsureDisplay - but need a variant that does not skip - // nodes that have shouldBypassEnsureDisplay set (such as image nodes) so they are rasterized. - [self recursivelyDisplayImmediately]; + if ([self _shouldAbortTransitionWithID:transitionID]) { + return; } - } else { - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode *node) { - if (shouldRasterize) { - [node enterHierarchyState:ASHierarchyStateRasterized]; - } else { - [node exitHierarchyState:ASHierarchyStateRasterized]; + + ASPerformBlockOnMainThread(^{ + ASLayoutTransition *pendingLayoutTransition; + _ASTransitionContext *pendingLayoutTransitionContext; + { + // Grab __instanceLock__ here to make sure this transition isn't invalidated + // right after it passed the validation test and before it proceeds + ASDN::MutexLocker l(__instanceLock__); + + if ([self _locked_shouldAbortTransitionWithID:transitionID]) { + return; + } + + // Update calculated layout + auto previousLayout = _calculatedDisplayNodeLayout; + auto pendingLayout = std::make_shared( + newLayout, + constrainedSize, + constrainedSize.max + ); + [self _locked_setCalculatedDisplayNodeLayout:pendingLayout]; + + // Setup pending layout transition for animation + _pendingLayoutTransition = pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self + pendingLayout:pendingLayout + previousLayout:previousLayout]; + // Setup context for pending layout transition. we need to hold a strong reference to the context + _pendingLayoutTransitionContext = pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated + layoutDelegate:_pendingLayoutTransition + completionDelegate:self]; } + + // Apply complete layout transitions for all subnodes + ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { + [node _completePendingLayoutTransition]; + node.hierarchyState &= (~ASHierarchyStateLayoutPending); + }); + + // Measurement pass completion + // Give the subclass a change to hook into before calling the completion block + [self _layoutTransitionMeasurementDidFinish]; + if (completion) { + completion(); + } + + // Apply the subnode insertion immediately to be able to animate the nodes + [pendingLayoutTransition applySubnodeInsertions]; + + // Kick off animating the layout transition + [self animateLayoutTransition:pendingLayoutTransitionContext]; + + // Mark transaction as finished + [self _finishOrCancelTransition]; }); + }; + + // Start transition based on flag on current or background thread + if (shouldMeasureAsync) { + ASPerformBlockOnBackgroundThread(transitionBlock); + } else { + transitionBlock(); } } -- (CGFloat)contentsScaleForDisplay +- (void)cancelLayoutTransition { - ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(__instanceLock__); + __instanceLock__.lock(); + BOOL transitionInProgress = _transitionInProgress; + __instanceLock__.unlock(); + + if (transitionInProgress) { + // Cancel transition in progress + [self _finishOrCancelTransition]; + + // Tell subnodes to exit layout pending state and clear related properties + ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { + node.hierarchyState &= (~ASHierarchyStateLayoutPending); + }); + } +} - return _contentsScaleForDisplay; +- (BOOL)_isTransitionInProgress +{ + ASDN::MutexLocker l(__instanceLock__); + return _transitionInProgress; } -- (void)setContentsScaleForDisplay:(CGFloat)contentsScaleForDisplay +- (BOOL)_isLayoutTransitionInvalid { - ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(__instanceLock__); + if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { + ASLayoutElementContext context = ASLayoutElementGetCurrentContext(); + if (ASLayoutElementContextIsNull(context) || _pendingTransitionID != context.transitionID) { + return YES; + } + } + return NO; +} - if (_contentsScaleForDisplay == contentsScaleForDisplay) - return; +/// Starts a new transition and returns the transition id +- (int32_t)_startNewTransition +{ + ASDN::MutexLocker l(__instanceLock__); + _transitionInProgress = YES; + _transitionID = OSAtomicAdd32(1, &_transitionID); + return _transitionID; +} - _contentsScaleForDisplay = contentsScaleForDisplay; +- (void)_layoutTransitionMeasurementDidFinish +{ + // No-Op in ASDisplayNode } -- (void)applyPendingViewState +- (void)_finishOrCancelTransition { - ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(__instanceLock__); + _transitionInProgress = NO; +} - // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout - // but automatic subnode management would require us to modify the node tree - // in the background on a loaded node, which isn't currently supported. - if (_pendingViewState.hasSetNeedsLayout) { - //Need to unlock before calling setNeedsLayout to avoid deadlocks. - //MutexUnlocker will re-lock at the end of scope. - ASDN::MutexUnlocker u(__instanceLock__); - [self __setNeedsLayout]; - } - - if (self.layerBacked) { - [_pendingViewState applyToLayer:self.layer]; - } else { - BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(_flags); - [_pendingViewState applyToView:self.view withSpecialPropertiesHandling:specialPropertiesHandling]; - } +- (void)setPendingTransitionID:(int32_t)pendingTransitionID +{ + ASDN::MutexLocker l(__instanceLock__); + ASDisplayNodeAssertTrue(_pendingTransitionID < pendingTransitionID); + _pendingTransitionID = pendingTransitionID; +} + +- (int32_t)pendingTransitionID +{ + ASDN::MutexLocker l(__instanceLock__); + return _pendingTransitionID; +} - // _ASPendingState objects can add up very quickly when adding - // many nodes. This is especially an issue in large collection views - // and table views. This needs to be weighed against the cost of - // reallocing a _ASPendingState. So in range managed nodes we - // delete the pending state, otherwise we just clear it. - if (ASHierarchyStateIncludesRangeManaged(_hierarchyState)) { - _pendingViewState = nil; - } else { - [_pendingViewState clearChanges]; - } +- (BOOL)_shouldAbortTransitionWithID:(int32_t)transitionID +{ + ASDN::MutexLocker l(__instanceLock__); + return [self _locked_shouldAbortTransitionWithID:transitionID]; } -- (void)displayImmediately +- (BOOL)_locked_shouldAbortTransitionWithID:(int32_t)transitionID { - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.synchronous, @"this method is designed for asynchronous mode only"); + return (!_transitionInProgress || _transitionID != transitionID); +} - [[self asyncLayer] displayImmediately]; +- (void)setDefaultLayoutTransitionDuration:(NSTimeInterval)defaultLayoutTransitionDuration +{ + ASDN::MutexLocker l(__instanceLock__); + _defaultLayoutTransitionDuration = defaultLayoutTransitionDuration; } -- (void)recursivelyDisplayImmediately +- (NSTimeInterval)defaultLayoutTransitionDuration { ASDN::MutexLocker l(__instanceLock__); - - for (ASDisplayNode *child in _subnodes) { - [child recursivelyDisplayImmediately]; - } - [self displayImmediately]; + return _defaultLayoutTransitionDuration; } -- (void)invalidateCalculatedLayout +- (void)setDefaultLayoutTransitionDelay:(NSTimeInterval)defaultLayoutTransitionDelay { ASDN::MutexLocker l(__instanceLock__); - - // This will cause the next layout pass to compute a new layout instead of returning - // the cached layout in case the constrained or parent size did not change - _calculatedDisplayNodeLayout->invalidate(); - if (_pendingDisplayNodeLayout != nullptr) { - _pendingDisplayNodeLayout->invalidate(); - } + _defaultLayoutTransitionDelay = defaultLayoutTransitionDelay; } -- (void)__setNeedsLayout +- (NSTimeInterval)defaultLayoutTransitionDelay { ASDN::MutexLocker l(__instanceLock__); - - [self invalidateCalculatedLayout]; + return _defaultLayoutTransitionDelay; } -- (void)__setNeedsDisplay +- (void)setDefaultLayoutTransitionOptions:(UIViewAnimationOptions)defaultLayoutTransitionOptions { - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); - // FIXME: This should not need to recursively display, so create a non-recursive variant. - // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive. - if (_layer && !_flags.synchronous && nowDisplay && [self __implementsDisplay]) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } + ASDN::MutexLocker l(__instanceLock__); + _defaultLayoutTransitionOptions = defaultLayoutTransitionOptions; } -// These private methods ensure that subclasses are not required to call super in order for _renderingSubnodes to be properly managed. - -- (void)__layout +- (UIViewAnimationOptions)defaultLayoutTransitionOptions { - ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(__instanceLock__); - CGRect bounds = _threadSafeBounds; - - 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. - LOG(@"Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); - return; - } - - // This method will confirm that the layout is up to date (and update if needed). - // Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning). - [self _locked_measureNodeWithBoundsIfNecessary:bounds]; - _pendingDisplayNodeLayout = nullptr; - - [self _locked_layoutPlaceholderIfNecessary]; - - [self layout]; - [self layoutDidFinish]; + return _defaultLayoutTransitionOptions; } -/// Needs to be called with lock held -- (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds +#pragma mark + +/* + * Hook for subclasses to perform an animation based on the given ASContextTransitioning. By default a fade in and out + * animation is provided. + */ +- (void)animateLayoutTransition:(id)context { - // Check if we are a subnode in a layout transition. - // In this case no measurement is needed as it's part of the layout transition - if ([self _isLayoutTransitionInvalid]) { + if ([context isAnimated] == NO) { + [self _layoutSublayouts]; + [context completeTransition:YES]; return; } + + ASDisplayNode *node = self; - CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size); + NSAssert(node.isNodeLoaded == YES, @"Invalid node state"); - // Prefer _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout (if exists, it's the newest) - // If there is no _pending, check if _calculated is valid to reuse (avoiding recalculation below). - if (_pendingDisplayNodeLayout == nullptr) { - if (_calculatedDisplayNodeLayout->isDirty() == NO - && (_calculatedDisplayNodeLayout->requestedLayoutFromAbove == YES - || CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) { - return; - } - } + NSArray *removedSubnodes = [context removedSubnodes]; + NSMutableArray *insertedSubnodes = [[context insertedSubnodes] mutableCopy]; + NSMutableArray *movedSubnodes = [NSMutableArray array]; - // _calculatedDisplayNodeLayout is not reusable we need to transition to a new one - [self cancelLayoutTransition]; + NSMutableArray<_ASAnimatedTransitionContext *> *insertedSubnodeContexts = [NSMutableArray array]; + NSMutableArray<_ASAnimatedTransitionContext *> *removedSubnodeContexts = [NSMutableArray array]; - BOOL didCreateNewContext = NO; - BOOL didOverrideExistingContext = NO; - BOOL shouldVisualizeLayout = ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); - ASLayoutElementContext context = ASLayoutElementGetCurrentContext(); - if (ASLayoutElementContextIsNull(context)) { - context = ASLayoutElementContextMake(ASLayoutElementContextDefaultTransitionID, shouldVisualizeLayout); - ASLayoutElementSetCurrentContext(context); - didCreateNewContext = YES; - } else { - if (context.needsVisualizeNode != shouldVisualizeLayout) { - context.needsVisualizeNode = shouldVisualizeLayout; - ASLayoutElementSetCurrentContext(context); - didOverrideExistingContext = YES; + for (ASDisplayNode *subnode in [context subnodesForKey:ASTransitionContextToLayoutKey]) { + if ([insertedSubnodes containsObject:subnode] == NO) { + // This is an existing subnode, check if it is resized, moved or both + CGRect fromFrame = [context initialFrameForNode:subnode]; + CGRect toFrame = [context finalFrameForNode:subnode]; + if (CGSizeEqualToSize(fromFrame.size, toFrame.size) == NO) { + [insertedSubnodes addObject:subnode]; + } + if (CGPointEqualToPoint(fromFrame.origin, toFrame.origin) == NO) { + [movedSubnodes addObject:subnode]; + } } } - // Figure out previous and pending layouts for layout transition - std::shared_ptr nextLayout = _pendingDisplayNodeLayout; - #define layoutSizeDifferentFromBounds !CGSizeEqualToSize(nextLayout->layout.size, boundsSizeForLayout) - - // nextLayout was likely created by a call to layoutThatFits:, check if is valid and can be applied. - // If our bounds size is different than it, or invalid, recalculate. Use #define to avoid nullptr-> - if (nextLayout == nullptr || nextLayout->isDirty() == YES || layoutSizeDifferentFromBounds) { - // Use the last known constrainedSize passed from a parent during layout (if never, use bounds). - ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass]; - ASLayout *layout = [self calculateLayoutThatFits:constrainedSize - restrictedToSize:self.style.size - relativeToParentSize:boundsSizeForLayout]; - - nextLayout = std::make_shared(layout, constrainedSize, boundsSizeForLayout); + // Create contexts for inserted and removed subnodes + for (ASDisplayNode *insertedSubnode in insertedSubnodes) { + [insertedSubnodeContexts addObject:[_ASAnimatedTransitionContext contextForNode:insertedSubnode alpha:insertedSubnode.alpha]]; } - - if (didCreateNewContext) { - ASLayoutElementClearCurrentContext(); - } else if (didOverrideExistingContext) { - context.needsVisualizeNode = !context.needsVisualizeNode; - ASLayoutElementSetCurrentContext(context); + for (ASDisplayNode *removedSubnode in removedSubnodes) { + [removedSubnodeContexts addObject:[_ASAnimatedTransitionContext contextForNode:removedSubnode alpha:removedSubnode.alpha]]; } - // If our new layout's desired size for self doesn't match current size, ask our parent to update it. - // This can occur for either pre-calculated or newly-calculated layouts. - if (nextLayout->requestedLayoutFromAbove == NO - && CGSizeEqualToSize(boundsSizeForLayout, nextLayout->layout.size) == NO) { - // The layout that we have specifies that this node (self) would like to be a different size - // than it currently is. Because that size has been computed within the constrainedSize, we - // expect that calling setNeedsLayoutFromAbove will result in our parent resizing us to this. - // However, in some cases apps may manually interfere with this (setting a different bounds). - // In this case, we need to detect that we've already asked to be resized to match this - // particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout. - nextLayout->requestedLayoutFromAbove = YES; - [self setNeedsLayoutFromAbove]; + // Fade out inserted subnodes + for (ASDisplayNode *insertedSubnode in insertedSubnodes) { + insertedSubnode.frame = [context finalFrameForNode:insertedSubnode]; + insertedSubnode.alpha = 0; } + + // Adjust groupOpacity for animation + BOOL originAllowsGroupOpacity = node.allowsGroupOpacity; + node.allowsGroupOpacity = YES; - // Prepare to transition to nextLayout - ASDisplayNodeAssertNotNil(nextLayout->layout, @"nextLayout->layout should not be nil! %@", self); - _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self - pendingLayout:nextLayout - previousLayout:_calculatedDisplayNodeLayout]; + [UIView animateWithDuration:self.defaultLayoutTransitionDuration delay:self.defaultLayoutTransitionDelay options:self.defaultLayoutTransitionOptions animations:^{ + // Fade removed subnodes and views out + for (ASDisplayNode *removedSubnode in removedSubnodes) { + removedSubnode.alpha = 0; + } + + // Fade inserted subnodes in + for (_ASAnimatedTransitionContext *insertedSubnodeContext in insertedSubnodeContexts) { + insertedSubnodeContext.node.alpha = insertedSubnodeContext.alpha; + } + + // Update frame of self and moved subnodes + CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size; + CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size; + BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO); + if (isResized == YES) { + CGPoint position = node.frame.origin; + node.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height); + } + for (ASDisplayNode *movedSubnode in movedSubnodes) { + movedSubnode.frame = [context finalFrameForNode:movedSubnode]; + } + } completion:^(BOOL finished) { + // Restore all removed subnode alpha values + for (_ASAnimatedTransitionContext *removedSubnodeContext in removedSubnodeContexts) { + removedSubnodeContext.node.alpha = removedSubnodeContext.alpha; + } + + // Restore group opacity + node.allowsGroupOpacity = originAllowsGroupOpacity; + + // Subnode removals are automatically performed + [context completeTransition:finished]; + }]; +} - // If a parent is currently executing a layout transition, perform our layout application after it. - if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) { - // If no transition, apply our new layout immediately (common case). - [self _completePendingLayoutTransition]; - } +/** + * Hook for subclasses to clean up nodes after the transition happened. Furthermore this can be used from subclasses + * to manually perform deletions. + */ +- (void)didCompleteLayoutTransition:(id)context +{ + __instanceLock__.lock(); + ASLayoutTransition *pendingLayoutTransition = _pendingLayoutTransition; + __instanceLock__.unlock(); + + [pendingLayoutTransition applySubnodeRemovals]; } -- (ASSizeRange)_locked_constrainedSizeForLayoutPass +#pragma mark <_ASTransitionContextCompletionDelegate> + +/** + * After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this + * delegate method will be called that start the completion process of the transition + */ +- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete { - // TODO: The logic in -setNeedsLayoutFromAbove seems correct and doesn't use this method. - // logic seems correct. For what case does -this method need to do the CGSizeEqual checks? - // IF WE CAN REMOVE BOUNDS CHECKS HERE, THEN WE CAN ALSO REMOVE "REQUESTED FROM ABOVE" CHECK - - CGSize boundsSizeForLayout = ASCeilSizeValues(self.threadSafeBounds.size); + ASDisplayNodeAssertMainThread(); + + [self didCompleteLayoutTransition:context]; - // Checkout if constrained size of pending or calculated display node layout can be used - if (_pendingDisplayNodeLayout != nullptr - && (_pendingDisplayNodeLayout->requestedLayoutFromAbove - || CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, boundsSizeForLayout))) { - // We assume the size from the last returned layoutThatFits: layout was applied so use the pending display node - // layout constrained size - return _pendingDisplayNodeLayout->constrainedSize; - } else if (_calculatedDisplayNodeLayout->layout != nil - && (_calculatedDisplayNodeLayout->requestedLayoutFromAbove - || CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) { - // We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different - return _calculatedDisplayNodeLayout->constrainedSize; - } else { - // In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can - // be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to - // the one returned from layoutThatFits: or layoutThatFits: was never called - return ASSizeRangeMake(boundsSizeForLayout); - } + _pendingLayoutTransitionContext = nil; + + [self _pendingLayoutTransitionDidComplete]; } -- (void)_locked_layoutPlaceholderIfNecessary +/** + * Completes the pending layout transition immediately without going through the the Layout Transition Animation API + */ +- (void)_completePendingLayoutTransition { - if ([self _shouldHavePlaceholderLayer]) { - [self _setupPlaceholderLayerIfNeeded]; + __instanceLock__.lock(); + ASLayoutTransition *pendingLayoutTransition = _pendingLayoutTransition; + __instanceLock__.unlock(); + + if (pendingLayoutTransition != nil) { + [self _setCalculatedDisplayNodeLayout:pendingLayoutTransition.pendingLayout]; + [self _completeLayoutTransition:pendingLayoutTransition]; } - // Update the placeholderLayer size in case the node size has changed since the placeholder was added. - _placeholderLayer.frame = self.threadSafeBounds; + [self _pendingLayoutTransitionDidComplete]; } -- (void)layoutDidFinish +/** + * Can be directly called to commit the given layout transition immediately to complete without calling through to the + * Layout Transition Animation API + */ +- (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition { - // Hook for subclasses + // Layout transition is not supported for nodes that are not have automatic subnode management enabled + if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) { + return; + } + + // Trampoline to the main thread if necessary + if (ASDisplayNodeThreadIsMain() || layoutTransition.isSynchronous == NO) { + [layoutTransition commitTransition]; + } else { + // Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded + ASPerformBlockOnMainThread(^{ + [layoutTransition commitTransition]; + }); + } } -- (CATransform3D)_transformToAncestor:(ASDisplayNode *)ancestor +- (void)_pendingLayoutTransitionDidComplete { - CATransform3D transform = CATransform3DIdentity; - ASDisplayNode *currentNode = self; - while (currentNode.supernode) { - if (currentNode == ancestor) { - return transform; - } - - CGPoint anchorPoint = currentNode.anchorPoint; - CGRect bounds = currentNode.bounds; - CGPoint position = currentNode.position; - CGPoint origin = CGPointMake(position.x - bounds.size.width * anchorPoint.x, - position.y - bounds.size.height * anchorPoint.y); + // Subclass hook + [self calculatedLayoutDidChange]; + + // Grab lock after calling out to subclass + ASDN::MutexLocker l(__instanceLock__); - transform = CATransform3DTranslate(transform, origin.x, origin.y, 0); - transform = CATransform3DTranslate(transform, -bounds.origin.x, -bounds.origin.y, 0); - currentNode = currentNode.supernode; + // 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 && !_placeholderImage && [self _locked_displaysAsynchronously]) { + + // Zero-sized nodes do not require a placeholder. + ASLayout *layout = _calculatedDisplayNodeLayout->layout; + CGSize layoutSize = (layout ? layout.size : CGSizeZero); + if (layoutSize.width * layoutSize.height <= 0.0) { + return; + } + + // If we've displayed our contents, we don't need a placeholder. + // Contents is a thread-affined property and can't be read off main after loading. + if (self.isNodeLoaded) { + ASPerformBlockOnMainThread(^{ + if (self.contents == nil) { + _placeholderImage = [self placeholderImage]; + } + }); + } else { + if (self.contents == nil) { + _placeholderImage = [self placeholderImage]; + } + } } - return transform; + + // Cleanup pending layout transition + _pendingLayoutTransition = nil; } -static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNode *referenceNode, ASDisplayNode *targetNode) +- (void)calculatedLayoutDidChange { - ASDisplayNode *ancestor = ASDisplayNodeFindClosestCommonAncestor(referenceNode, targetNode); + // subclass override +} - // Transform into global (away from reference coordinate space) - CATransform3D transformToGlobal = [referenceNode _transformToAncestor:ancestor]; +#pragma mark - Display - // Transform into local (via inverse transform from target to ancestor) - CATransform3D transformToLocal = CATransform3DInvert([targetNode _transformToAncestor:ancestor]); +NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; +NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp"; - return CATransform3DConcat(transformToGlobal, transformToLocal); +- (BOOL)displaysAsynchronously +{ + ASDN::MutexLocker l(__instanceLock__); + return [self _locked_displaysAsynchronously]; } -- (CGPoint)convertPoint:(CGPoint)point fromNode:(ASDisplayNode *)node +/** + * Core implementation of -displaysAsynchronously. + */ +- (BOOL)_locked_displaysAsynchronously { - ASDisplayNodeAssertThreadAffinity(self); - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to point - return CGPointApplyAffineTransform(point, flattenedTransform); + return _flags.synchronous == NO && _flags.displaysAsynchronously; } -- (CGPoint)convertPoint:(CGPoint)point toNode:(ASDisplayNode *)node +- (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously { ASDisplayNodeAssertThreadAffinity(self); - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); + + ASDN::MutexLocker l(__instanceLock__); - // Apply to point - return CGPointApplyAffineTransform(point, flattenedTransform); -} + // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel) + if (_flags.synchronous) { + return; + } -- (CGRect)convertRect:(CGRect)rect fromNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); + if (_flags.displaysAsynchronously == displaysAsynchronously) { + return; + } - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); + _flags.displaysAsynchronously = displaysAsynchronously; - // Apply to rect - return CGRectApplyAffineTransform(rect, flattenedTransform); + self._locked_asyncLayer.displaysAsynchronously = displaysAsynchronously; } -- (CGRect)convertRect:(CGRect)rect toNode:(ASDisplayNode *)node +- (BOOL)shouldRasterizeDescendants { - ASDisplayNodeAssertThreadAffinity(self); - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to rect - return CGRectApplyAffineTransform(rect, flattenedTransform); + ASDN::MutexLocker l(__instanceLock__); + ASDisplayNodeAssert(!((_hierarchyState & ASHierarchyStateRasterized) && _flags.shouldRasterizeDescendants), + @"Subnode of a rasterized node should not have redundant shouldRasterizeDescendants enabled"); + return _flags.shouldRasterizeDescendants; } -#pragma mark - _ASDisplayLayerDelegate - -- (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer asynchronously:(BOOL)asynchronously +- (void)setShouldRasterizeDescendants:(BOOL)shouldRasterize { - // Subclass hook. - [self displayWillStart]; - [self displayWillStartAsynchronously:asynchronously]; + ASDisplayNodeAssertThreadAffinity(self); + BOOL rasterizedFromSelfOrAncestor = NO; + { + ASDN::MutexLocker l(__instanceLock__); + + if (_flags.shouldRasterizeDescendants == shouldRasterize) + return; + + _flags.shouldRasterizeDescendants = shouldRasterize; + rasterizedFromSelfOrAncestor = shouldRasterize || ASHierarchyStateIncludesRasterized(_hierarchyState); + } + + if (self.isNodeLoaded) { + // Recursively tear down or build up subnodes. + // TODO: When disabling rasterization, preserve rasterized backing store as placeholderImage + // while the newly materialized subtree finishes rendering. Then destroy placeholderImage to save memory. + [self recursivelyClearContents]; + + ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode *node) { + if (rasterizedFromSelfOrAncestor) { + [node enterHierarchyState:ASHierarchyStateRasterized]; + if (node.isNodeLoaded) { + [node __unloadNode]; + } + } else { + [node exitHierarchyState:ASHierarchyStateRasterized]; + // We can avoid eagerly loading this node. We will load it on-demand as usual. + } + }); + if (!rasterizedFromSelfOrAncestor) { + // If we are not going to rasterize at all, go ahead and set up our view hierarchy. + [self _addSubnodeViewsAndLayers]; + } + + if (ASInterfaceStateIncludesVisible(self.interfaceState)) { + // TODO: Change this to recursivelyEnsureDisplay - but need a variant that does not skip + // nodes that have shouldBypassEnsureDisplay set (such as image nodes) so they are rasterized. + [self recursivelyDisplayImmediately]; + } + } else { + ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode *node) { + if (rasterizedFromSelfOrAncestor) { + [node enterHierarchyState:ASHierarchyStateRasterized]; + } else { + [node exitHierarchyState:ASHierarchyStateRasterized]; + } + }); + } } -- (void)didDisplayAsyncLayer:(_ASDisplayLayer *)layer +- (CGFloat)contentsScaleForDisplay { - // Subclass hook. - [self displayDidFinish]; -} + ASDN::MutexLocker l(__instanceLock__); -#pragma mark - CALayerDelegate + return _contentsScaleForDisplay; +} -// We are only the delegate for the layer when we are layer-backed, as UIView performs this funcition normally -- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event +- (void)setContentsScaleForDisplay:(CGFloat)contentsScaleForDisplay { - if (event == kCAOnOrderIn) { - [self __enterHierarchy]; - } else if (event == kCAOnOrderOut) { - [self __exitHierarchy]; + ASDN::MutexLocker l(__instanceLock__); + + if (_contentsScaleForDisplay == contentsScaleForDisplay) { + return; } - ASDisplayNodeAssert(_flags.layerBacked, @"We shouldn't get called back here if there is no layer"); - return (id)kCFNull; + _contentsScaleForDisplay = contentsScaleForDisplay; } -#pragma mark - Managing the Node Hierarchy +- (void)displayImmediately +{ + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(!_flags.synchronous, @"this method is designed for asynchronous mode only"); -ASDISPLAYNODE_INLINE bool shouldDisableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASDisplayNode *to) { - if (!from || !to) return NO; - if (from->_flags.synchronous) return NO; - if (to->_flags.synchronous) return NO; - if (from->_flags.isInHierarchy != to->_flags.isInHierarchy) return NO; - return YES; + [self.asyncLayer displayImmediately]; } -/// Returns incremented value of i if i is not NSNotFound -ASDISPLAYNODE_INLINE NSInteger incrementIfFound(NSInteger i) { - return i == NSNotFound ? NSNotFound : i + 1; +- (void)recursivelyDisplayImmediately +{ + for (ASDisplayNode *child in self.subnodes) { + [child recursivelyDisplayImmediately]; + } + [self displayImmediately]; } -/// Returns if a node is a member of a rasterized tree -ASDISPLAYNODE_INLINE BOOL canUseViewAPI(ASDisplayNode *node, ASDisplayNode *subnode) { - return (subnode.isLayerBacked == NO && node.isLayerBacked == NO); +- (void)__setNeedsDisplay +{ + BOOL shouldScheduleForDisplay = NO; + { + ASDN::MutexLocker l(__instanceLock__); + BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); + // FIXME: This should not need to recursively display, so create a non-recursive variant. + // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive. + if (_layer != nil && !_flags.synchronous && nowDisplay && [self _implementsDisplay]) { + shouldScheduleForDisplay = YES; + } + } + + if (shouldScheduleForDisplay) { + [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; + } } -/// Returns if node is a member of a rasterized tree -ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) { - return (node->_flags.shouldRasterizeDescendants || (node->_hierarchyState & ASHierarchyStateRasterized)); ++ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node +{ + static dispatch_once_t onceToken; + static ASRunLoopQueue *renderQueue; + dispatch_once(&onceToken, ^{ + renderQueue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() + retainObjects:NO + handler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) { + [dequeuedItem _recursivelyTriggerDisplayAndBlock:NO]; + if (isQueueDrained) { + CFTimeInterval timestamp = CACurrentMediaTime(); + [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification + object:nil + userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}]; + } + }]; + }); + + [renderQueue enqueue:node]; } -/* - * Central private helper method that should eventually be called if submethods add, insert or replace subnodes - * You must hold __instanceLock__ to call this. - * - * @param subnode The subnode to insert - * @param subnodeIndex The index in _subnodes to insert it - * @param viewSublayerIndex The index in layer.sublayers (not view.subviews) at which to insert the view (use if we can use the view API) otherwise pass NSNotFound - * @param sublayerIndex The index in layer.sublayers at which to insert the layer (use if either parent or subnode is layer-backed) otherwise pass NSNotFound - * @param oldSubnode Remove this subnode before inserting; ok to be nil if no removal is desired - */ -- (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnodeIndex sublayerIndex:(NSInteger)sublayerIndex andRemoveSubnode:(ASDisplayNode *)oldSubnode +/// Helper method to summarize whether or not the node run through the display process +- (BOOL)_implementsDisplay { - if (subnode == nil || subnode == self) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode or self as subnode"); - return; - } - - if (subnodeIndex == NSNotFound) { - ASDisplayNodeFailAssert(@"Try to insert node on an index that was not found"); - return; - } - - if (subnodeIndex > _subnodes.count || subnodeIndex < 0) { - ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %zd. Count is %zd", subnodeIndex, _subnodes.count); - return; - } - - // Disable appearance methods during move between supernodes, but make sure we restore their state after we do our thing - ASDisplayNode *oldParent = subnode.supernode; - BOOL disableNotifications = shouldDisableNotificationsForMovingBetweenParents(oldParent, self); - if (disableNotifications) { - [subnode __incrementVisibilityNotificationsDisabled]; - } - - [subnode _removeFromSupernode]; - [oldSubnode _removeFromSupernode]; - - if (_subnodes == nil) { - _subnodes = [[NSMutableArray alloc] init]; - } - - [_subnodes insertObject:subnode atIndex:subnodeIndex]; - - // This call will apply our .hierarchyState to the new subnode. - // If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState. - [subnode __setSupernode:self]; + ASDN::MutexLocker l(__instanceLock__); - // Don't bother inserting the view/layer if in a rasterized subtree, because there are no layers in the hierarchy and - // none of this could possibly work. - if (nodeIsInRasterizedTree(self) == NO && self.nodeLoaded) { - // If node is loaded insert the subnode otherwise wait until the node get's loaded - ASPerformBlockOnMainThread(^{ - [self _insertSubnodeSubviewOrSublayer:subnode atIndex:sublayerIndex]; - }); + return _flags.implementsDrawRect || _flags.implementsImageDisplay || _flags.shouldRasterizeDescendants || + _flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay; +} + +// Track that a node will be displayed as part of the current node hierarchy. +// The node sending the message should usually be passed as the parameter, similar to the delegation pattern. +- (void)_pendingNodeWillDisplay:(ASDisplayNode *)node +{ + ASDisplayNodeAssertMainThread(); + + // No lock needed as _pendingDisplayNodes is main thread only + if (!_pendingDisplayNodes) { + _pendingDisplayNodes = [[ASWeakSet alloc] init]; } - ASDisplayNodeAssert(disableNotifications == shouldDisableNotificationsForMovingBetweenParents(oldParent, self), @"Invariant violated"); - if (disableNotifications) { - [subnode __decrementVisibilityNotificationsDisabled]; - } + [_pendingDisplayNodes addObject:node]; } -/* - * Inserts the view or layer of the given node at the given index - * You must hold __instanceLock__ to call this. - * - * @param subnode The subnode to insert - * @param idx The index in _view.subviews or _layer.sublayers at which to insert the subnode.view or - * subnode.layer of the subnode - */ -- (void)_insertSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode atIndex:(NSInteger)idx +// Notify that a node that was pending display finished +// The node sending the message should usually be passed as the parameter, similar to the delegation pattern. +- (void)_pendingNodeDidDisplay:(ASDisplayNode *)node { ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(self.nodeLoaded, @"_insertSubnodeSubviewOrSublayer:atIndex: should never be called before our own view is created"); - ASDisplayNodeAssert(idx != NSNotFound, @"Try to insert node on an index that was not found"); - if (idx == NSNotFound) { - return; - } - - // If we can use view API, do. Due to an apple bug, -insertSubview:atIndex: actually wants a LAYER index, which we pass in - if (canUseViewAPI(self, subnode)) { - [_view insertSubview:subnode.view atIndex:idx]; - } else { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - [_layer insertSublayer:subnode.layer atIndex:idx]; -#pragma clang diagnostic pop + // No lock for _pendingDisplayNodes needed as it's main thread only + [_pendingDisplayNodes removeObject:node]; + + if (_pendingDisplayNodes.isEmpty) { + + [self hierarchyDisplayDidFinish]; + BOOL placeholderShouldPersist = [self placeholderShouldPersist]; + + __instanceLock__.lock(); + if (_placeholderLayer.superlayer && !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(); + } + } + __instanceLock__.unlock(); } } -- (void)addSubnode:(ASDisplayNode *)subnode +- (void)hierarchyDisplayDidFinish { - ASDisplayNodeLogEvent(self, @"addSubnode: %@", subnode); - // TODO: 2.0 Conversion: Reenable and fix within product code - //ASDisplayNodeAssert(self.automaticallyManagesSubnodes == NO, @"Attempt to manually add subnode to node with automaticallyManagesSubnodes=YES. Node: %@", subnode); - [self _addSubnode:subnode]; + // Subclass hook } -- (void)_addSubnode:(ASDisplayNode *)subnode +// Helper method to determine if it's safe to call setNeedsDisplay on a layer without throwing away the content. +// For details look at the comment on the canCallSetNeedsDisplayOfLayer flag +- (BOOL)_canCallSetNeedsDisplayOfLayer { - ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(__instanceLock__); + return _flags.canCallSetNeedsDisplayOfLayer; +} + +void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) +{ + // This recursion must handle layers in various states: + // 1. Just added to hierarchy, CA hasn't yet called -display + // 2. Previously in a hierarchy (such as a working window owned by an Intelligent Preloading class, like ASTableView / ASCollectionView / ASViewController) + // 3. Has no content to display at all + // Specifically for case 1), we need to explicitly trigger a -display call now. + // Otherwise, there is no opportunity to block the main thread after CoreAnimation's transaction commit + // (even a runloop observer at a late call order will not stop the next frame from compositing, showing placeholders). - ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode"); - - // Don't add subnode if it's already if it's already a subnodes - ASDisplayNode *oldParent = subnode.supernode; - if (!subnode || subnode == self || oldParent == self) { - return; + ASDisplayNode *node = [layer asyncdisplaykit_node]; + + if (node.isSynchronous && [node _canCallSetNeedsDisplayOfLayer]) { + // Layers for UIKit components that are wrapped within a node needs to be set to be displayed as the contents of + // the layer get's cleared and would not be recreated otherwise. + // We do not call this for _ASDisplayLayer as an optimization. + [layer setNeedsDisplay]; + } + + 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]; + } + + // Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work. + // NOTE: The docs report that `sublayers` returns a copy but it actually doesn't. + for (CALayer *sublayer in [layer.sublayers copy]) { + recursivelyTriggerDisplayForLayer(sublayer, shouldBlock); + } + + if (shouldBlock) { + // As the recursion unwinds, verify each transaction is complete and block if it is not. + // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first. + BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay); + if (waitUntilComplete) { + for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) { + // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main. + // This significantly reduces time on the main thread relative to UIKit. + [transaction waitUntilComplete]; + } + } } - - [self _insertSubnode:subnode atSubnodeIndex:_subnodes.count sublayerIndex:_layer.sublayers.count andRemoveSubnode:nil]; } -- (void)_addSubnodeViewsAndLayers +- (void)_recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock { - for (ASDisplayNode *node in [_subnodes copy]) { - [self _addSubnodeSubviewOrSublayer:node]; + ASDisplayNodeAssertMainThread(); + + CALayer *layer = self.layer; + // -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout, + // so we should call it outside of starting the recursion below. If our own layer is not marked + // as dirty, we can assume layout has run on this subtree before. + if ([layer needsLayout]) { + [layer layoutIfNeeded]; } + recursivelyTriggerDisplayForLayer(layer, shouldBlock); } -- (void)_addSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode +- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously { - // Due to a bug in Apple's framework we have to use the layer index to insert a subview - // so just use th ecount of the sublayers to add the subnode - NSInteger idx = _layer.sublayers.count; - [self _insertSubnodeSubviewOrSublayer:subnode atIndex:idx]; + [self _recursivelyTriggerDisplayAndBlock:synchronously]; } -- (void)replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode +- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay { - ASDisplayNodeLogEvent(self, @"replaceSubnode: %@ withSubnode:%@", oldSubnode, replacementSubnode); - // TODO: 2.0 Conversion: Reenable and fix within product code - //ASDisplayNodeAssert(self.automaticallyManagesSubnodes == NO, @"Attempt to manually replace old node with replacement node to node with automaticallyManagesSubnodes=YES. Old Node: %@, replacement node: %@", oldSubnode, replacementSubnode); - [self _replaceSubnode:oldSubnode withSubnode:replacementSubnode]; + ASDN::MutexLocker l(__instanceLock__); + _flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay; } -- (void)_replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode +- (BOOL)shouldBypassEnsureDisplay { - ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(__instanceLock__); + return _flags.shouldBypassEnsureDisplay; +} - if (replacementSubnode == nil) { - ASDisplayNodeFailAssert(@"Invalid subnode to replace"); - return; - } - - if ([oldSubnode _deallocSafeSupernode] != self) { - ASDisplayNodeFailAssert(@"Old Subnode to replace must be a subnode"); - return; - } - - ASDisplayNodeAssert(!(self.nodeLoaded && !oldSubnode.nodeLoaded), @"We have view loaded, but child node does not."); - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - - NSInteger subnodeIndex = [_subnodes indexOfObjectIdenticalTo:oldSubnode]; - NSInteger sublayerIndex = NSNotFound; - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (nodeIsInRasterizedTree(self) == NO) { - if (_layer) { - sublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:oldSubnode.layer]; - ASDisplayNodeAssert(sublayerIndex != NSNotFound, @"Somehow oldSubnode's supernode is self, yet we could not find it in our layers to replace"); - if (sublayerIndex == NSNotFound) { - return; - } +- (void)setNeedsDisplayAtScale:(CGFloat)contentsScale +{ + { + ASDN::MutexLocker l(__instanceLock__); + if (contentsScale == _contentsScaleForDisplay) { + return; } + + _contentsScaleForDisplay = contentsScale; } - [self _insertSubnode:replacementSubnode atSubnodeIndex:subnodeIndex sublayerIndex:sublayerIndex andRemoveSubnode:oldSubnode]; + [self setNeedsDisplay]; } -- (void)insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below +- (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale { - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ belowSubnode:%@", subnode, below); - // TODO: 2.0 Conversion: Reenable and fix within product code - //ASDisplayNodeAssert(self.automaticallyManagesSubnodes == NO, @"Attempt to manually insert subnode to node with automaticallyManagesSubnodes=YES. Node: %@", subnode); - [self _insertSubnode:subnode belowSubnode:below]; + ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { + [node setNeedsDisplayAtScale:contentsScale]; + }); } -- (void)_insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below +- (void)recursivelySetDisplaySuspended:(BOOL)flag { - ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(__instanceLock__); + _recursivelySetDisplaySuspended(self, nil, flag); +} - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; +// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or a variant with a condition / test block. +static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, BOOL flag) +{ + // If there is no layer, but node whose its view is loaded, then we can traverse down its layer hierarchy. Otherwise we must stick to the node hierarchy to avoid loading views prematurely. Note that for nodes that haven't loaded their views, they can't possibly have subviews/sublayers, so we don't need to traverse the layer hierarchy for them. + if (!layer && node && node.nodeLoaded) { + layer = node.layer; } - if ([below _deallocSafeSupernode] != self) { - ASDisplayNodeFailAssert(@"Node to insert below must be a subnode"); - return; + // If we don't know the node, but the layer is an async layer, get the node from the layer. + if (!node && layer && [layer isKindOfClass:[_ASDisplayLayer class]]) { + node = layer.asyncdisplaykit_node; } - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - - NSInteger belowSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:below]; - NSInteger belowSublayerIndex = NSNotFound; + // Set the flag on the node. If this is a pure layer (no node) then this has no effect (plain layers don't support preventing/cancelling display). + node.displaySuspended = flag; - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (nodeIsInRasterizedTree(self) == NO) { - if (_layer) { - belowSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:below.layer]; - ASDisplayNodeAssert(belowSublayerIndex != NSNotFound, @"Somehow below's supernode is self, yet we could not find it in our layers to reference"); - if (belowSublayerIndex == NSNotFound) - return; + if (layer && !node.shouldRasterizeDescendants) { + // If there is a layer, recurse down the layer hierarchy to set the flag on descendants. This will cover both layer-based and node-based children. + for (CALayer *sublayer in layer.sublayers) { + _recursivelySetDisplaySuspended(nil, sublayer, flag); } - - ASDisplayNodeAssert(belowSubnodeIndex != NSNotFound, @"Couldn't find above in subnodes"); - - // If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to - // insert it will mess up our calculation - if ([subnode _deallocSafeSupernode] == self) { - NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode]; - if (currentIndexInSubnodes < belowSubnodeIndex) { - belowSubnodeIndex--; - } - if (_layer) { - NSInteger currentIndexInSublayers = [_layer.sublayers indexOfObjectIdenticalTo:subnode.layer]; - if (currentIndexInSublayers < belowSublayerIndex) { - belowSublayerIndex--; - } - } + } else { + // If there is no layer (view not loaded yet) or this node rasterizes descendants (there won't be a layer tree to traverse), recurse down the subnode hierarchy to set the flag on descendants. This covers only node-based children, but for a node whose view is not loaded it can't possibly have nodeless children. + for (ASDisplayNode *subnode in node.subnodes) { + _recursivelySetDisplaySuspended(subnode, nil, flag); } } - - ASDisplayNodeAssert(belowSubnodeIndex != NSNotFound, @"Couldn't find below in subnodes"); - - [self _insertSubnode:subnode atSubnodeIndex:belowSubnodeIndex sublayerIndex:belowSublayerIndex andRemoveSubnode:nil]; } -- (void)insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above +- (BOOL)displaySuspended { - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ abodeSubnode: %@", subnode, above); - // TODO: 2.0 Conversion: Reenable and fix within product code - //ASDisplayNodeAssert(self.automaticallyManagesSubnodes == NO, @"Attempt to manually insert subnode to node with automaticallyManagesSubnodes=YES. Node: %@", subnode); - [self _insertSubnode:subnode aboveSubnode:above]; + ASDN::MutexLocker l(__instanceLock__); + return _flags.displaySuspended; } -- (void)_insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above +- (void)setDisplaySuspended:(BOOL)flag { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(__instanceLock__); - - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; - } + __instanceLock__.lock(); - if ([above _deallocSafeSupernode] != self) { - ASDisplayNodeFailAssert(@"Node to insert above must be a subnode"); + // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel) + if (_flags.synchronous || _flags.displaySuspended == flag) { + __instanceLock__.unlock(); return; } - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); + _flags.displaySuspended = flag; - NSInteger aboveSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:above]; - NSInteger aboveSublayerIndex = NSNotFound; + self._locked_asyncLayer.displaySuspended = flag; + + ASDisplayNode *supernode = _supernode; + __instanceLock__.unlock(); - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (nodeIsInRasterizedTree(self) == NO) { - if (_layer) { - aboveSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:above.layer]; - ASDisplayNodeAssert(aboveSublayerIndex != NSNotFound, @"Somehow above's supernode is self, yet we could not find it in our layers to replace"); - if (aboveSublayerIndex == NSNotFound) - return; - } - - ASDisplayNodeAssert(aboveSubnodeIndex != NSNotFound, @"Couldn't find above in subnodes"); - - // If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to - // insert it will mess up our calculation - if ([subnode _deallocSafeSupernode] == self) { - NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode]; - if (currentIndexInSubnodes <= aboveSubnodeIndex) { - aboveSubnodeIndex--; - } - if (_layer) { - NSInteger currentIndexInSublayers = [_layer.sublayers indexOfObjectIdenticalTo:subnode.layer]; - if (currentIndexInSublayers <= aboveSublayerIndex) { - aboveSublayerIndex--; - } + if ([self _implementsDisplay]) { + // Display start and finish methods needs to happen on the main thread + ASPerformBlockOnMainThread(^{ + if (flag) { + [supernode subnodeDisplayDidFinish:self]; + } else { + [supernode subnodeDisplayWillStart:self]; } - } + }); } - - [self _insertSubnode:subnode atSubnodeIndex:incrementIfFound(aboveSubnodeIndex) sublayerIndex:incrementIfFound(aboveSublayerIndex) andRemoveSubnode:nil]; -} - -- (void)insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx -{ - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ atIndex: %td", subnode, idx); - // TODO: 2.0 Conversion: Reenable and fix within product code - //ASDisplayNodeAssert(self.automaticallyManagesSubnodes == NO, @"Attempt to manually insert subnode to node with automaticallyManagesSubnodes=YES. Node: %@", subnode); - [self _insertSubnode:subnode atIndex:idx]; } -- (void)_insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx +NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; +static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; + +- (void)setDrawingPriority:(NSInteger)drawingPriority { ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(__instanceLock__); - - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; - } - - if (idx > _subnodes.count || idx < 0) { - ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %zd. Count is %zd", idx, _subnodes.count); - return; - } - - NSInteger sublayerIndex = NSNotFound; - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (nodeIsInRasterizedTree(self) == NO) { - // Account for potentially having other subviews - if (_layer && idx == 0) { - sublayerIndex = 0; - } else if (_layer) { - ASDisplayNode *positionInRelationTo = (_subnodes.count > 0 && idx > 0) ? _subnodes[idx - 1] : nil; - if (positionInRelationTo) { - sublayerIndex = incrementIfFound([_layer.sublayers indexOfObjectIdenticalTo:positionInRelationTo.layer]); - } - } + if (drawingPriority == ASDefaultDrawingPriority) { + _flags.hasCustomDrawingPriority = NO; + objc_setAssociatedObject(self, ASDisplayNodeDrawingPriorityKey, nil, OBJC_ASSOCIATION_ASSIGN); + } else { + _flags.hasCustomDrawingPriority = YES; + objc_setAssociatedObject(self, ASDisplayNodeDrawingPriorityKey, @(drawingPriority), OBJC_ASSOCIATION_RETAIN); } - - [self _insertSubnode:subnode atSubnodeIndex:idx sublayerIndex:sublayerIndex andRemoveSubnode:nil]; } -- (void)_removeSubnode:(ASDisplayNode *)subnode +- (NSInteger)drawingPriority { ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(__instanceLock__); - - // Don't call self.supernode here because that will retain/autorelease the supernode. This method -_removeSupernode: is often called while tearing down a node hierarchy, and the supernode in question might be in the middle of its -dealloc. The supernode is never messaged, only compared by value, so this is safe. - // The particular issue that triggers this edge case is when a node calls -removeFromSupernode on a subnode from within its own -dealloc method. - if (!subnode || [subnode _deallocSafeSupernode] != self) { - return; + + if (!_flags.hasCustomDrawingPriority) { + return ASDefaultDrawingPriority; + } else { + return [objc_getAssociatedObject(self, ASDisplayNodeDrawingPriorityKey) integerValue]; } +} - [_subnodes removeObjectIdenticalTo:subnode]; - [subnode __setSupernode:nil]; +#pragma mark <_ASDisplayLayerDelegate> + +- (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer asynchronously:(BOOL)asynchronously +{ + // Subclass hook. + [self displayWillStart]; + [self displayWillStartAsynchronously:asynchronously]; } -- (void)removeFromSupernode +- (void)didDisplayAsyncLayer:(_ASDisplayLayer *)layer { - //ASDisplayNodeAssert(self.supernode.automaticallyManagesSubnodes == NO, @"Attempt to manually remove subnode from node with automaticallyManagesSubnodes=YES. Node: %@", self); - - [self _removeFromSupernode]; + // Subclass hook. + [self displayDidFinish]; } -// NOTE: You must not called this method while holding the receiver's property lock. This may cause deadlocks. -- (void)_removeFromSupernode +- (void)displayWillStart {} +- (void)displayWillStartAsynchronously:(BOOL)asynchronously { - ASDisplayNodeAssertThreadAffinity(self); + [self displayWillStart]; // Subclass override + ASDisplayNodeAssertMainThread(); + + ASDisplayNodeLogEvent(self, @"displayWillStart"); + // in case current node takes longer to display than it's subnodes, treat it as a dependent node + [self _pendingNodeWillDisplay:self]; __instanceLock__.lock(); - __weak ASDisplayNode *supernode = _supernode; - __weak UIView *view = _view; - __weak CALayer *layer = _layer; - BOOL layerBacked = _flags.layerBacked; - BOOL isNodeLoaded = (layer != nil || view != nil); + ASDisplayNode *supernode = _supernode; __instanceLock__.unlock(); + + [supernode subnodeDisplayWillStart:self]; +} - // Clear supernode's reference to us before removing the view from the hierarchy, as _ASDisplayView - // will trigger us to clear our _supernode pointer in willMoveToSuperview:nil. - // This may result in removing the last strong reference, triggering deallocation after this method. - [supernode _removeSubnode:self]; +- (void)displayDidFinish +{ + ASDisplayNodeAssertMainThread(); + + ASDisplayNodeLogEvent(self, @"displayDidFinish"); + [self _pendingNodeDidDisplay:self]; - if (isNodeLoaded && (supernode == nil || supernode.isNodeLoaded)) { - ASPerformBlockOnMainThread(^{ - if (layerBacked || supernode.layerBacked) { - [layer removeFromSuperlayer]; - } else { - [view removeFromSuperview]; - } - }); - } + __instanceLock__.lock(); + ASDisplayNode *supernode = _supernode; + __instanceLock__.unlock(); + + [supernode subnodeDisplayDidFinish:self]; } -- (BOOL)__visibilityNotificationsDisabled +- (void)subnodeDisplayWillStart:(ASDisplayNode *)subnode { - // Currently, this method is only used by the testing infrastructure to verify this internal feature. - ASDN::MutexLocker l(__instanceLock__); - return _flags.visibilityNotificationsDisabled > 0; + // Subclass hook + [self _pendingNodeWillDisplay:subnode]; } -- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled +- (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode { - ASDN::MutexLocker l(__instanceLock__); - return (_hierarchyState & ASHierarchyStateTransitioningSupernodes); + // Subclass hook + [self _pendingNodeDidDisplay:subnode]; } -- (void)__incrementVisibilityNotificationsDisabled +#pragma mark + +// We are only the delegate for the layer when we are layer-backed, as UIView performs this function normally +- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event { - ASDN::MutexLocker l(__instanceLock__); - const size_t maxVisibilityIncrement = (1ULL< 0, @"Can't decrement past 0"); - if (_flags.visibilityNotificationsDisabled > 0) { - _flags.visibilityNotificationsDisabled--; + if (_nonFatalErrorBlock != nonFatalErrorBlock) { + _nonFatalErrorBlock = [nonFatalErrorBlock copy]; } - if (_flags.visibilityNotificationsDisabled == 0) { - // Must have just transitioned from 1 to 0. Notify all subnodes that we are no longer in a disabled state. - // FIXME: This system should be revisited when refactoring and consolidating the implementation of the - // addSubnode: and insertSubnode:... methods. As implemented, though logically irrelevant for expected use cases, - // multiple nodes in the subtree below may have a non-zero visibilityNotification count and still have - // the ASHierarchyState bit cleared (the only value checked when reading this state). - [self exitHierarchyState:ASHierarchyStateTransitioningSupernodes]; +} + ++ (ASDisplayNodeNonFatalErrorBlock)nonFatalErrorBlock +{ + return _nonFatalErrorBlock; +} + +#pragma mark - Converting to and from the Node's Coordinate System + +- (CATransform3D)_transformToAncestor:(ASDisplayNode *)ancestor +{ + CATransform3D transform = CATransform3DIdentity; + ASDisplayNode *currentNode = self; + while (currentNode.supernode) { + if (currentNode == ancestor) { + return transform; + } + + CGPoint anchorPoint = currentNode.anchorPoint; + CGRect bounds = currentNode.bounds; + CGPoint position = currentNode.position; + CGPoint origin = CGPointMake(position.x - bounds.size.width * anchorPoint.x, + position.y - bounds.size.height * anchorPoint.y); + + transform = CATransform3DTranslate(transform, origin.x, origin.y, 0); + transform = CATransform3DTranslate(transform, -bounds.origin.x, -bounds.origin.y, 0); + currentNode = currentNode.supernode; } + return transform; } -- (void)__enterHierarchy +static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNode *referenceNode, ASDisplayNode *targetNode) { - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"Should not cause recursive __enterHierarchy"); - - // 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(__instanceLock__); + ASDisplayNode *ancestor = ASDisplayNodeFindClosestCommonAncestor(referenceNode, targetNode); + + // Transform into global (away from reference coordinate space) + CATransform3D transformToGlobal = [referenceNode _transformToAncestor:ancestor]; + + // Transform into local (via inverse transform from target to ancestor) + CATransform3D transformToLocal = CATransform3DInvert([targetNode _transformToAncestor:ancestor]); + + return CATransform3DConcat(transformToGlobal, transformToLocal); +} + +- (CGPoint)convertPoint:(CGPoint)point fromNode:(ASDisplayNode *)node +{ + ASDisplayNodeAssertThreadAffinity(self); - if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { - _flags.isEnteringHierarchy = YES; - _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]; + /** + * When passed node=nil, all methods in this family use the UIView-style + * behavior – that is, convert from/to window coordinates if there's a window, + * otherwise return the point untransformed. + */ + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertPoint:point fromLayer:window.layer]; } else { - [self willEnterHierarchy]; - } - _flags.isEnteringHierarchy = NO; - - - // 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]; - } + return point; } } + + // Get root node of the accessible node hierarchy, if node not specified + node = node ? : ASDisplayNodeUltimateParentOfNode(self); + + // Calculate transform to map points between coordinate spaces + CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); + CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); + ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); + + // Apply to point + return CGPointApplyAffineTransform(point, flattenedTransform); } -- (void)__exitHierarchy +- (CGPoint)convertPoint:(CGPoint)point toNode:(ASDisplayNode *)node { - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"Should not cause recursive __exitHierarchy"); + ASDisplayNodeAssertThreadAffinity(self); - // 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(__instanceLock__); + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertPoint:point toLayer:window.layer]; + } else { + return point; + } + } - if (_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { - _flags.isExitingHierarchy = YES; - _flags.isInHierarchy = NO; + // Get root node of the accessible node hierarchy, if node not specified + node = node ? : ASDisplayNodeUltimateParentOfNode(self); + + // Calculate transform to map points between coordinate spaces + CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); + CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); + ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - [self.asyncLayer cancelAsyncDisplay]; + // Apply to point + return CGPointApplyAffineTransform(point, flattenedTransform); +} - 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]; +- (CGRect)convertRect:(CGRect)rect fromNode:(ASDisplayNode *)node +{ + ASDisplayNodeAssertThreadAffinity(self); + + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertRect:rect fromLayer:window.layer]; } else { - [self didExitHierarchy]; + return rect; } - - _flags.isExitingHierarchy = NO; } + + // Get root node of the accessible node hierarchy, if node not specified + node = node ? : ASDisplayNodeUltimateParentOfNode(self); + + // Calculate transform to map points between coordinate spaces + CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); + CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); + ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); + + // Apply to rect + return CGRectApplyAffineTransform(rect, flattenedTransform); } -- (void)_recursiveWillEnterHierarchy +- (CGRect)convertRect:(CGRect)rect toNode:(ASDisplayNode *)node { - if (_flags.visibilityNotificationsDisabled) { - return; + ASDisplayNodeAssertThreadAffinity(self); + + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertRect:rect toLayer:window.layer]; + } else { + return rect; + } } + + // Get root node of the accessible node hierarchy, if node not specified + node = node ? : ASDisplayNodeUltimateParentOfNode(self); - _flags.isEnteringHierarchy = YES; - [self willEnterHierarchy]; - _flags.isEnteringHierarchy = NO; + // Calculate transform to map points between coordinate spaces + CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); + CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); + ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - for (ASDisplayNode *subnode in self.subnodes) { - [subnode _recursiveWillEnterHierarchy]; - } + // Apply to rect + return CGRectApplyAffineTransform(rect, flattenedTransform); } -- (void)_recursiveDidExitHierarchy -{ - if (_flags.visibilityNotificationsDisabled) { - return; - } +#pragma mark - Managing the Node Hierarchy - _flags.isExitingHierarchy = YES; - [self didExitHierarchy]; - _flags.isExitingHierarchy = NO; +ASDISPLAYNODE_INLINE bool shouldDisableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASDisplayNode *to) { + if (!from || !to) return NO; + if (from.isSynchronous) return NO; + if (to.isSynchronous) return NO; + if (from.isInHierarchy != to.isInHierarchy) return NO; + return YES; +} - for (ASDisplayNode *subnode in self.subnodes) { - [subnode _recursiveDidExitHierarchy]; - } +/// Returns incremented value of i if i is not NSNotFound +ASDISPLAYNODE_INLINE NSInteger incrementIfFound(NSInteger i) { + return i == NSNotFound ? NSNotFound : i + 1; } -- (NSArray *)subnodes -{ - ASDN::MutexLocker l(__instanceLock__); - return ([_subnodes copy] ?: @[]); +/// Returns if a node is a member of a rasterized tree +ASDISPLAYNODE_INLINE BOOL canUseViewAPI(ASDisplayNode *node, ASDisplayNode *subnode) { + return (subnode.isLayerBacked == NO && node.isLayerBacked == NO); } -- (ASDisplayNode *)supernode -{ - ASDN::MutexLocker l(__instanceLock__); - return _supernode; +/// Returns if node is a member of a rasterized tree +ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) { + return (node.shouldRasterizeDescendants || (node.hierarchyState & ASHierarchyStateRasterized)); } -// This is a thread-method to return the supernode without causing it to be retained autoreleased. See -_removeSubnode: for details. -- (ASDisplayNode *)_deallocSafeSupernode +// NOTE: This method must be dealloc-safe (should not retain self). +- (ASDisplayNode *)supernode { +#if CHECK_LOCKING_SAFETY + if (__instanceLock__.ownedByCurrentThread()) { + NSLog(@"WARNING: Accessing supernode while holding recursive instance lock of this node is worrisome. It's likely that you will soon try to acquire the supernode's lock, and this can easily cause deadlocks."); + } +#endif + ASDN::MutexLocker l(__instanceLock__); return _supernode; } -- (void)__setSupernode:(ASDisplayNode *)newSupernode +- (void)_setSupernode:(ASDisplayNode *)newSupernode { BOOL supernodeDidChange = NO; ASDisplayNode *oldSupernode = nil; @@ -2449,562 +2666,853 @@ - (void)__setSupernode:(ASDisplayNode *)newSupernode // Propagate down the new pending transition id ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - node.pendingTransitionID = _pendingTransitionID; + node.pendingTransitionID = pendingTransitionId; }); } } } // Now that we have a supernode, propagate its traits to self. - ASEnvironmentStatePropagateDown(self, [newSupernode environmentTraitCollection]); + ASTraitCollectionPropagateDown(self, newSupernode.primitiveTraitCollection); + } else { // If a node will be removed from the supernode it should go out from the layout pending state to remove all // layout pending state related properties on the node stateToEnterOrExit |= ASHierarchyStateLayoutPending; [self exitHierarchyState:stateToEnterOrExit]; + + // We only need to explicitly exit hierarchy here if we were rasterized. + // Otherwise we will exit the hierarchy when our view/layer does so + // which has some nice carry-over machinery to handle cases where we are removed from a hierarchy + // and then added into it again shortly after. + __instanceLock__.lock(); + BOOL isInHierarchy = _flags.isInHierarchy; + __instanceLock__.unlock(); + + if (parentWasOrIsRasterized && isInHierarchy) { + [self __exitHierarchy]; + } } } } -// Track that a node will be displayed as part of the current node hierarchy. -// The node sending the message should usually be passed as the parameter, similar to the delegation pattern. -- (void)_pendingNodeWillDisplay:(ASDisplayNode *)node +- (NSArray *)subnodes { - ASDisplayNodeAssertMainThread(); - - if (!_pendingDisplayNodes) { - _pendingDisplayNodes = [[ASWeakSet alloc] init]; - } - - [_pendingDisplayNodes addObject:node]; + ASDN::MutexLocker l(__instanceLock__); + return ([_subnodes copy] ?: @[]); } -// Notify that a node that was pending display finished -// The node sending the message should usually be passed as the parameter, similar to the delegation pattern. -- (void)_pendingNodeDidDisplay:(ASDisplayNode *)node +/* + * Central private helper method that should eventually be called if submethods add, insert or replace subnodes + * This method is called with thread affinity. + * + * @param subnode The subnode to insert + * @param subnodeIndex The index in _subnodes to insert it + * @param viewSublayerIndex The index in layer.sublayers (not view.subviews) at which to insert the view (use if we can use the view API) otherwise pass NSNotFound + * @param sublayerIndex The index in layer.sublayers at which to insert the layer (use if either parent or subnode is layer-backed) otherwise pass NSNotFound + * @param oldSubnode Remove this subnode before inserting; ok to be nil if no removal is desired + */ +- (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnodeIndex sublayerIndex:(NSInteger)sublayerIndex andRemoveSubnode:(ASDisplayNode *)oldSubnode { - ASDisplayNodeAssertMainThread(); - - [_pendingDisplayNodes removeObject:node]; + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + + if (subnode == nil || subnode == self) { + ASDisplayNodeFailAssert(@"Cannot insert a nil subnode or self as subnode"); + return; + } + + if (subnodeIndex == NSNotFound) { + ASDisplayNodeFailAssert(@"Try to insert node on an index that was not found"); + return; + } + + if (self.layerBacked && !subnode.layerBacked) { + ASDisplayNodeFailAssert(@"Cannot add a view-backed node as a subnode of a layer-backed node. Supernode: %@, subnode: %@", self, subnode); + return; + } - if (_pendingDisplayNodes.isEmpty) { - [self hierarchyDisplayDidFinish]; - - if (_placeholderLayer.superlayer && ![self placeholderShouldPersist]) { - void (^cleanupBlock)() = ^{ - [_placeholderLayer removeFromSuperlayer]; - }; + __instanceLock__.lock(); + NSUInteger subnodesCount = _subnodes.count; + __instanceLock__.unlock(); + if (subnodeIndex > subnodesCount || subnodeIndex < 0) { + ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %zd. Count is %zd", subnodeIndex, subnodesCount); + return; + } + + // Disable appearance methods during move between supernodes, but make sure we restore their state after we do our thing + ASDisplayNode *oldParent = subnode.supernode; + BOOL disableNotifications = shouldDisableNotificationsForMovingBetweenParents(oldParent, self); + if (disableNotifications) { + [subnode __incrementVisibilityNotificationsDisabled]; + } + + [subnode _removeFromSupernode]; + [oldSubnode _removeFromSupernode]; + + __instanceLock__.lock(); + if (_subnodes == nil) { + _subnodes = [[NSMutableArray alloc] init]; + } + [_subnodes insertObject:subnode atIndex:subnodeIndex]; + __instanceLock__.unlock(); + + // This call will apply our .hierarchyState to the new subnode. + // If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState. + [subnode _setSupernode:self]; - if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) { - [CATransaction begin]; - [CATransaction setCompletionBlock:cleanupBlock]; - [CATransaction setAnimationDuration:_placeholderFadeDuration]; - _placeholderLayer.opacity = 0.0; - [CATransaction commit]; - } else { - cleanupBlock(); + // If this subnode will be rasterized, update its hierarchy state & enter hierarchy if needed + if (nodeIsInRasterizedTree(self)) { + ASDisplayNodePerformBlockOnEveryNodeBFS(subnode, ^(ASDisplayNode * _Nonnull node) { + [node enterHierarchyState:ASHierarchyStateRasterized]; + if (node.isNodeLoaded) { + [node __unloadNode]; } + }); + if (self.isInHierarchy) { + [subnode __enterHierarchy]; } + } else if (self.nodeLoaded) { + // If not rasterizing, and node is loaded insert the subview/sublayer now. + [self _insertSubnodeSubviewOrSublayer:subnode atIndex:sublayerIndex]; + } // Otherwise we will insert subview/sublayer when we get loaded + + ASDisplayNodeAssert(disableNotifications == shouldDisableNotificationsForMovingBetweenParents(oldParent, self), @"Invariant violated"); + if (disableNotifications) { + [subnode __decrementVisibilityNotificationsDisabled]; } } -/// Helper method to summarize whether or not the node run through the display process -- (BOOL)__implementsDisplay +/* + * Inserts the view or layer of the given node at the given index + * + * @param subnode The subnode to insert + * @param idx The index in _view.subviews or _layer.sublayers at which to insert the subnode.view or + * subnode.layer of the subnode + */ +- (void)_insertSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode atIndex:(NSInteger)idx { - return _flags.implementsDrawRect || _flags.implementsImageDisplay || _flags.shouldRasterizeDescendants || - _flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay; -} + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(self.nodeLoaded, @"_insertSubnodeSubviewOrSublayer:atIndex: should never be called before our own view is created"); -// Helper method to determine if it's safe to call setNeedsDisplay on a layer without throwing away the content. -// For details look at the comment on the canCallSetNeedsDisplayOfLayer flag -- (BOOL)__canCallSetNeedsDisplayOfLayer -{ - return _flags.canCallSetNeedsDisplayOfLayer; -} + ASDisplayNodeAssert(idx != NSNotFound, @"Try to insert node on an index that was not found"); + if (idx == NSNotFound) { + return; + } + + // Because the view and layer can only be created and destroyed on Main, that is also the only thread + // where the view and layer can change. We can avoid locking. -- (BOOL)placeholderShouldPersist -{ - return NO; + // If we can use view API, do. Due to an apple bug, -insertSubview:atIndex: actually wants a LAYER index, + // which we pass in. + if (canUseViewAPI(self, subnode)) { + [_view insertSubview:subnode.view atIndex:idx]; + } else { + [_layer insertSublayer:subnode.layer atIndex:(unsigned int)idx]; + } } -- (BOOL)_shouldHavePlaceholderLayer +- (void)addSubnode:(ASDisplayNode *)subnode { - return (_placeholderEnabled && [self __implementsDisplay]); + ASDisplayNodeLogEvent(self, @"addSubnode: %@", subnode); + // TODO: 2.0 Conversion: Reenable and fix within product code + //ASDisplayNodeAssert(self.automaticallyManagesSubnodes == NO, @"Attempt to manually add subnode to node with automaticallyManagesSubnodes=YES. Node: %@", subnode); + [self _addSubnode:subnode]; } -- (void)_setupPlaceholderLayerIfNeeded +- (void)_addSubnode:(ASDisplayNode *)subnode { - ASDisplayNodeAssertMainThread(); - - 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; + ASDisplayNodeAssertThreadAffinity(self); + + ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode"); + + // Don't add if it's already a subnode + ASDisplayNode *oldParent = subnode.supernode; + if (!subnode || subnode == self || oldParent == self) { + return; } - 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; - } - } + NSUInteger subnodesIndex; + NSUInteger sublayersIndex; + { + ASDN::MutexLocker l(__instanceLock__); + subnodesIndex = _subnodes.count; + sublayersIndex = _layer.sublayers.count; } + + [self _insertSubnode:subnode atSubnodeIndex:subnodesIndex sublayerIndex:sublayersIndex andRemoveSubnode:nil]; } -void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) +- (void)_addSubnodeViewsAndLayers { - // This recursion must handle layers in various states: - // 1. Just added to hierarchy, CA hasn't yet called -display - // 2. Previously in a hierarchy (such as a working window owned by an Intelligent Preloading class, like ASTableView / ASCollectionView / ASViewController) - // 3. Has no content to display at all - // Specifically for case 1), we need to explicitly trigger a -display call now. - // Otherwise, there is no opportunity to block the main thread after CoreAnimation's transaction commit - // (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 (node.isSynchronous && [node __canCallSetNeedsDisplayOfLayer]) { - // Layers for UIKit components that are wrapped within a node needs to be set to be displayed as the contents of - // the layer get's cleared and would not be recreated otherwise. - // We do not call this for _ASDisplayLayer as an optimization. - [layer setNeedsDisplay]; - } - - 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]; - } + ASDisplayNodeAssertMainThread(); - // Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work. - // NOTE: The docs report that `sublayers` returns a copy but it actually doesn't. - for (CALayer *sublayer in [layer.sublayers copy]) { - recursivelyTriggerDisplayForLayer(sublayer, shouldBlock); - } + TIME_SCOPED(_debugTimeToAddSubnodeViews); - if (shouldBlock) { - // As the recursion unwinds, verify each transaction is complete and block if it is not. - // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first. - BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay); - if (waitUntilComplete) { - for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) { - // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main. - // This significantly reduces time on the main thread relative to UIKit. - [transaction waitUntilComplete]; - } - } + for (ASDisplayNode *node in self.subnodes) { + [self _addSubnodeSubviewOrSublayer:node]; } } -- (void)_recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock +- (void)_addSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode { ASDisplayNodeAssertMainThread(); - CALayer *layer = self.layer; - // -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout, - // so we should call it outside of starting the recursion below. If our own layer is not marked - // as dirty, we can assume layout has run on this subtree before. - if ([layer needsLayout]) { - [layer layoutIfNeeded]; - } - recursivelyTriggerDisplayForLayer(layer, shouldBlock); -} - -- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously -{ - [self _recursivelyTriggerDisplayAndBlock:synchronously]; -} - -- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay -{ - _flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay; -} - -- (BOOL)shouldBypassEnsureDisplay -{ - return _flags.shouldBypassEnsureDisplay; + // Due to a bug in Apple's framework we have to use the layer index to insert a subview + // so just use the count of the sublayers to add the subnode + NSInteger idx = _layer.sublayers.count; // No locking is needed as it's main thread only + [self _insertSubnodeSubviewOrSublayer:subnode atIndex:idx]; } -#pragma mark - For Subclasses - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize - restrictedToSize:(ASLayoutElementSize)size - relativeToParentSize:(CGSize)parentSize +- (void)replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode { - const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, ASLayoutElementSizeResolve(self.style.size, parentSize)); - return [self calculateLayoutThatFits:resolvedRange]; + ASDisplayNodeLogEvent(self, @"replaceSubnode: %@ withSubnode:%@", oldSubnode, replacementSubnode); + // TODO: 2.0 Conversion: Reenable and fix within product code + //ASDisplayNodeAssert(self.automaticallyManagesSubnodes == NO, @"Attempt to manually replace old node with replacement node to node with automaticallyManagesSubnodes=YES. Old Node: %@, replacement node: %@", oldSubnode, replacementSubnode); + [self _replaceSubnode:oldSubnode withSubnode:replacementSubnode]; } -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize +- (void)_replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode { - __ASDisplayNodeCheckForLayoutMethodOverrides; - - ASDN::MutexLocker l(__instanceLock__); + ASDisplayNodeAssertThreadAffinity(self); - // Manual size calculation via calculateSizeThatFits: - if (((_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) || - (_layoutSpecBlock != NULL)) == NO) { - CGSize size = [self calculateSizeThatFits:constrainedSize.max]; - ASDisplayNodeLogEvent(self, @"calculatedSize: %@", NSStringFromCGSize(size)); - return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil]; + if (replacementSubnode == nil) { + ASDisplayNodeFailAssert(@"Invalid subnode to replace"); + return; } - // Size calcualtion with layout elements - BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; - if (measureLayoutSpec) { - _layoutSpecNumberOfPasses++; + if (oldSubnode.supernode != self) { + ASDisplayNodeFailAssert(@"Old Subnode to replace must be a subnode"); + return; } - // Get layout element from the node - id layoutElement = [self _layoutElementThatFits:constrainedSize]; + ASDisplayNodeAssert(!(self.nodeLoaded && !oldSubnode.nodeLoaded), @"We have view loaded, but child node does not."); - // Certain properties are necessary to set on an element of type ASLayoutSpec - if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) { - ASLayoutSpec *layoutSpec = (ASLayoutSpec *)layoutElement; + NSInteger subnodeIndex; + NSInteger sublayerIndex = NSNotFound; + { + ASDN::MutexLocker l(__instanceLock__); + ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - NSSet *duplicateElements = [layoutSpec findDuplicatedElementsInSubtree]; - if (duplicateElements.count > 0) { - ASDisplayNodeFailAssert(@"Node %@ returned a layout spec that contains the same elements in multiple positions. Elements: %@", self, duplicateElements); - // Use an empty layout spec to avoid crashes - layoutSpec = [[ASLayoutSpec alloc] init]; - } - - if (_shouldCacheLayoutSpec) { - _layoutSpec = layoutSpec; - } else { - ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec); - } + subnodeIndex = [_subnodes indexOfObjectIdenticalTo:oldSubnode]; - layoutSpec.parent = self; - layoutSpec.isMutable = NO; + // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the + // hierarchy and none of this could possibly work. + if (nodeIsInRasterizedTree(self) == NO) { + if (_layer) { + sublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:oldSubnode.layer]; + ASDisplayNodeAssert(sublayerIndex != NSNotFound, @"Somehow oldSubnode's supernode is self, yet we could not find it in our layers to replace"); + if (sublayerIndex == NSNotFound) { + return; + } + } + } } - - // Manually propagate the trait collection here so that any layoutSpec children of layoutSpec will get a traitCollection - { - ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); - ASEnvironmentStatePropagateDown(layoutElement, [self environmentTraitCollection]); + + [self _insertSubnode:replacementSubnode atSubnodeIndex:subnodeIndex sublayerIndex:sublayerIndex andRemoveSubnode:oldSubnode]; +} + +- (void)insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below +{ + ASDisplayNodeLogEvent(self, @"insertSubnode: %@ belowSubnode:%@", subnode, below); + // TODO: 2.0 Conversion: Reenable and fix within product code + //ASDisplayNodeAssert(self.automaticallyManagesSubnodes == NO, @"Attempt to manually insert subnode to node with automaticallyManagesSubnodes=YES. Node: %@", subnode); + [self _insertSubnode:subnode belowSubnode:below]; +} + +- (void)_insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below +{ + ASDisplayNodeAssertThreadAffinity(self); + + if (subnode == nil) { + ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); + return; } - - BOOL measureLayoutComputation = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation; - if (measureLayoutComputation) { - _layoutComputationNumberOfPasses++; + + if (below.supernode != self) { + ASDisplayNodeFailAssert(@"Node to insert below must be a subnode"); + return; } - // Layout element layout creation - ASLayout *layout = ({ - ASDN::SumScopeTimer t(_layoutComputationTotalTime, measureLayoutComputation); - [layoutElement layoutThatFits:constrainedSize]; - }); - ASDisplayNodeAssertNotNil(layout, @"[ASLayoutElement layoutThatFits:] should never return nil! %@, %@", self, layout); + NSInteger belowSubnodeIndex; + NSInteger belowSublayerIndex = NSNotFound; + { + ASDN::MutexLocker l(__instanceLock__); + ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - // Make sure layoutElementObject of the root layout is `self`, so that the flattened layout will be structurally correct. - BOOL isFinalLayoutElement = (layout.layoutElement != self); - if (isFinalLayoutElement) { - layout.position = CGPointZero; - layout = [ASLayout layoutWithLayoutElement:self size:layout.size sublayouts:@[layout]]; + belowSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:below]; + + // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the + // hierarchy and none of this could possibly work. + if (nodeIsInRasterizedTree(self) == NO) { + if (_layer) { + belowSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:below.layer]; + ASDisplayNodeAssert(belowSublayerIndex != NSNotFound, @"Somehow below's supernode is self, yet we could not find it in our layers to reference"); + if (belowSublayerIndex == NSNotFound) + return; + } + + ASDisplayNodeAssert(belowSubnodeIndex != NSNotFound, @"Couldn't find above in subnodes"); + + // If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to + // insert it will mess up our calculation + if (subnode.supernode == self) { + NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode]; + if (currentIndexInSubnodes < belowSubnodeIndex) { + belowSubnodeIndex--; + } + if (_layer) { + NSInteger currentIndexInSublayers = [_layer.sublayers indexOfObjectIdenticalTo:subnode.layer]; + if (currentIndexInSublayers < belowSublayerIndex) { + belowSublayerIndex--; + } + } + } + } } - ASDisplayNodeLogEvent(self, @"computedLayout: %@", layout); - return [layout filteredNodeLayoutTree]; + ASDisplayNodeAssert(belowSubnodeIndex != NSNotFound, @"Couldn't find below in subnodes"); + + [self _insertSubnode:subnode atSubnodeIndex:belowSubnodeIndex sublayerIndex:belowSublayerIndex andRemoveSubnode:nil]; } -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize +- (void)insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above { - __ASDisplayNodeCheckForLayoutMethodOverrides; - -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - if (ASIsCGSizeValidForSize(constrainedSize) == NO) { - NSLog(@"Cannot calculate size of node: constrainedSize is infinite and node does not override -calculateSizeThatFits: or specify a preferredSize. Try setting style.preferredSize. Node: %@", [self displayNodeRecursiveDescription]); - } -#endif - - return ASIsCGSizeValidForSize(constrainedSize) ? constrainedSize : CGSizeZero; + ASDisplayNodeLogEvent(self, @"insertSubnode: %@ abodeSubnode: %@", subnode, above); + // TODO: 2.0 Conversion: Reenable and fix within product code + //ASDisplayNodeAssert(self.automaticallyManagesSubnodes == NO, @"Attempt to manually insert subnode to node with automaticallyManagesSubnodes=YES. Node: %@", subnode); + [self _insertSubnode:subnode aboveSubnode:above]; } -- (id)_layoutElementThatFits:(ASSizeRange)constrainedSize +- (void)_insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above { - __ASDisplayNodeCheckForLayoutMethodOverrides; - - BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; - - if (_shouldCacheLayoutSpec && _layoutSpec != nil) { - return _layoutSpec; - } else if (_layoutSpecBlock != NULL) { - return ({ - ASDN::MutexLocker l(__instanceLock__); - ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); - _layoutSpecBlock(self, constrainedSize); - }); - } else { - return ({ - ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); - [self layoutSpecThatFits:constrainedSize]; - }); + ASDisplayNodeAssertThreadAffinity(self); + + if (subnode == nil) { + ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); + return; + } + + if (above.supernode != self) { + ASDisplayNodeFailAssert(@"Node to insert above must be a subnode"); + return; + } + + NSInteger aboveSubnodeIndex; + NSInteger aboveSublayerIndex = NSNotFound; + { + ASDN::MutexLocker l(__instanceLock__); + ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); + + aboveSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:above]; + + // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the + // hierarchy and none of this could possibly work. + if (nodeIsInRasterizedTree(self) == NO) { + if (_layer) { + aboveSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:above.layer]; + ASDisplayNodeAssert(aboveSublayerIndex != NSNotFound, @"Somehow above's supernode is self, yet we could not find it in our layers to replace"); + if (aboveSublayerIndex == NSNotFound) + return; + } + + ASDisplayNodeAssert(aboveSubnodeIndex != NSNotFound, @"Couldn't find above in subnodes"); + + // If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to + // insert it will mess up our calculation + if (subnode.supernode == self) { + NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode]; + if (currentIndexInSubnodes <= aboveSubnodeIndex) { + aboveSubnodeIndex--; + } + if (_layer) { + NSInteger currentIndexInSublayers = [_layer.sublayers indexOfObjectIdenticalTo:subnode.layer]; + if (currentIndexInSublayers <= aboveSublayerIndex) { + aboveSublayerIndex--; + } + } + } + } } + + [self _insertSubnode:subnode atSubnodeIndex:incrementIfFound(aboveSubnodeIndex) sublayerIndex:incrementIfFound(aboveSublayerIndex) andRemoveSubnode:nil]; } -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +- (void)insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx { - __ASDisplayNodeCheckForLayoutMethodOverrides; - - ASDisplayNodeAssert(NO, @"-[ASDisplayNode layoutSpecThatFits:] should never return an empty value. One way this is caused is by calling -[super layoutSpecThatFits:] which is not currently supported."); - return [[ASLayoutSpec alloc] init]; + ASDisplayNodeLogEvent(self, @"insertSubnode: %@ atIndex: %td", subnode, idx); + // TODO: 2.0 Conversion: Reenable and fix within product code + //ASDisplayNodeAssert(self.automaticallyManagesSubnodes == NO, @"Attempt to manually insert subnode to node with automaticallyManagesSubnodes=YES. Node: %@", subnode); + [self _insertSubnode:subnode atIndex:idx]; } -- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock +- (void)_insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx { - // For now there should never be an overwrite of layoutSpecThatFits: / layoutElementThatFits: and a layoutSpecBlock - ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits), @"Overwriting layoutSpecThatFits: and providing a layoutSpecBlock block is currently not supported"); + ASDisplayNodeAssertThreadAffinity(self); + + if (subnode == nil) { + ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); + return; + } - ASDN::MutexLocker l(__instanceLock__); - _layoutSpecBlock = layoutSpecBlock; + NSInteger sublayerIndex = NSNotFound; + { + ASDN::MutexLocker l(__instanceLock__); + + if (idx > _subnodes.count || idx < 0) { + ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %zd. Count is %zd", idx, _subnodes.count); + return; + } + + // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the + // hierarchy and none of this could possibly work. + if (nodeIsInRasterizedTree(self) == NO) { + // Account for potentially having other subviews + if (_layer && idx == 0) { + sublayerIndex = 0; + } else if (_layer) { + ASDisplayNode *positionInRelationTo = (_subnodes.count > 0 && idx > 0) ? _subnodes[idx - 1] : nil; + if (positionInRelationTo) { + sublayerIndex = incrementIfFound([_layer.sublayers indexOfObjectIdenticalTo:positionInRelationTo.layer]); + } + } + } + } + + [self _insertSubnode:subnode atSubnodeIndex:idx sublayerIndex:sublayerIndex andRemoveSubnode:nil]; } -- (ASLayoutSpecBlock)layoutSpecBlock +- (void)_removeSubnode:(ASDisplayNode *)subnode { - ASDN::MutexLocker l(__instanceLock__); - return _layoutSpecBlock; + ASDisplayNodeAssertThreadAffinity(self); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + + // Don't call self.supernode here because that will retain/autorelease the supernode. This method -_removeSupernode: is often called while tearing down a node hierarchy, and the supernode in question might be in the middle of its -dealloc. The supernode is never messaged, only compared by value, so this is safe. + // The particular issue that triggers this edge case is when a node calls -removeFromSupernode on a subnode from within its own -dealloc method. + if (!subnode || subnode.supernode != self) { + return; + } + + __instanceLock__.lock(); + [_subnodes removeObjectIdenticalTo:subnode]; + __instanceLock__.unlock(); + + [subnode _setSupernode:nil]; } -- (ASLayout *)calculatedLayout +- (void)removeFromSupernode { - ASDN::MutexLocker l(__instanceLock__); - return _calculatedDisplayNodeLayout->layout; + // TODO: 2.0 Conversion: Reenable and fix within product code + //ASDisplayNodeAssert(self.supernode.automaticallyManagesSubnodes == NO, @"Attempt to manually remove subnode from node with automaticallyManagesSubnodes=YES. Node: %@", self); + + [self _removeFromSupernode]; } -- (void)setCalculatedDisplayNodeLayout:(std::shared_ptr)displayNodeLayout +- (void)_removeFromSupernode { - ASDN::MutexLocker l(__instanceLock__); - - ASDisplayNodeAssertTrue(displayNodeLayout->layout.layoutElement == self); - ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.width >= 0.0); - ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.height >= 0.0); + ASDisplayNodeAssertThreadAffinity(self); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); - _calculatedDisplayNodeLayout = displayNodeLayout; -} + __instanceLock__.lock(); + __weak ASDisplayNode *supernode = _supernode; + __weak UIView *view = _view; + __weak CALayer *layer = _layer; + __instanceLock__.unlock(); -- (CGSize)calculatedSize -{ - ASDN::MutexLocker l(__instanceLock__); - if (_pendingDisplayNodeLayout != nullptr) { - return _pendingDisplayNodeLayout->layout.size; - } - return _calculatedDisplayNodeLayout->layout.size; + [self _removeFromSupernode:supernode view:view layer:layer]; } -- (ASSizeRange)constrainedSizeForCalculatedLayout +- (void)_removeFromSupernodeIfEqualTo:(ASDisplayNode *)supernode { - ASDN::MutexLocker l(__instanceLock__); - if (_pendingDisplayNodeLayout != nullptr) { - return _pendingDisplayNodeLayout->constrainedSize; - } - return _calculatedDisplayNodeLayout->constrainedSize; + ASDisplayNodeAssertThreadAffinity(self); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + + __instanceLock__.lock(); + + // Only remove if supernode is still the expected supernode + if (!ASObjectIsEqual(_supernode, supernode)) { + __instanceLock__.unlock(); + return; + } + + __weak UIView *view = _view; + __weak CALayer *layer = _layer; + __instanceLock__.unlock(); + + [self _removeFromSupernode:supernode view:view layer:layer]; } -- (void)setPendingTransitionID:(int32_t)pendingTransitionID +- (void)_removeFromSupernode:(ASDisplayNode *)supernode view:(UIView *)view layer:(CALayer *)layer { - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodeAssertTrue(_pendingTransitionID < pendingTransitionID); - _pendingTransitionID = pendingTransitionID; -} + // Clear supernode's reference to us before removing the view from the hierarchy, as _ASDisplayView + // will trigger us to clear our _supernode pointer in willMoveToSuperview:nil. + // This may result in removing the last strong reference, triggering deallocation after this method. + [supernode _removeSubnode:self]; -- (int32_t)pendingTransitionID -{ - ASDN::MutexLocker l(__instanceLock__); - return _pendingTransitionID; + if (view != nil) { + [view removeFromSuperview]; + } else if (layer != nil) { + [layer removeFromSuperlayer]; + } } -- (CGRect)threadSafeBounds +#pragma mark - Visibility API + +- (BOOL)__visibilityNotificationsDisabled { + // Currently, this method is only used by the testing infrastructure to verify this internal feature. ASDN::MutexLocker l(__instanceLock__); - return _threadSafeBounds; + return _flags.visibilityNotificationsDisabled > 0; } -- (void)setThreadSafeBounds:(CGRect)newBounds +- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled { ASDN::MutexLocker l(__instanceLock__); - _threadSafeBounds = newBounds; + return (_hierarchyState & ASHierarchyStateTransitioningSupernodes); } -- (UIImage *)placeholderImage +- (void)__incrementVisibilityNotificationsDisabled { - return nil; + __instanceLock__.lock(); + const size_t maxVisibilityIncrement = (1ULL< 0, @"Can't decrement past 0"); + if (_flags.visibilityNotificationsDisabled > 0) { + _flags.visibilityNotificationsDisabled--; + } + BOOL visibilityNotificationsDisabled = (_flags.visibilityNotificationsDisabled == 0); + __instanceLock__.unlock(); + + if (visibilityNotificationsDisabled) { + // Must have just transitioned from 1 to 0. Notify all subnodes that we are no longer in a disabled state. + // FIXME: This system should be revisited when refactoring and consolidating the implementation of the + // addSubnode: and insertSubnode:... methods. As implemented, though logically irrelevant for expected use cases, + // multiple nodes in the subtree below may have a non-zero visibilityNotification count and still have + // the ASHierarchyState bit cleared (the only value checked when reading this state). + [self exitHierarchyState:ASHierarchyStateTransitioningSupernodes]; } - _onDidLoadBlocks = nil; - [self didLoad]; } -- (void)didLoad +#pragma mark - Placeholder + +- (void)_locked_layoutPlaceholderIfNecessary { - ASDisplayNodeAssertMainThread(); + if ([self _locked_shouldHavePlaceholderLayer]) { + [self _locked_setupPlaceholderLayerIfNeeded]; + } + // Update the placeholderLayer size in case the node size has changed since the placeholder was added. + _placeholderLayer.frame = self.threadSafeBounds; } -#pragma mark Hierarchy State +- (BOOL)_locked_shouldHavePlaceholderLayer +{ + return (_placeholderEnabled && [self _implementsDisplay]); +} -- (void)willEnterHierarchy +- (void)_locked_setupPlaceholderLayerIfNeeded { ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(_flags.isEnteringHierarchy, @"You should never call -willEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); - ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - if (![self supportsRangeManagedInterfaceState]) { - self.interfaceState = ASInterfaceStateInHierarchy; + 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)didExitHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode"); - ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - - if (![self supportsRangeManagedInterfaceState]) { - self.interfaceState = ASInterfaceStateNone; - } else { - // This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange: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(__instanceLock__); - if (!_flags.isInHierarchy && ASInterfaceStateIncludesVisible(_interfaceState)) { - self.interfaceState = (_interfaceState & ~ASInterfaceStateVisible); - } - }); + 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; + } } } } -#pragma mark Interface State - -- (void)clearContents +- (UIImage *)placeholderImage { - if (_flags.canClearContentsOfLayer) { - // No-op if these haven't been created yet, as that guarantees they don't have contents that needs to be released. - _layer.contents = nil; - } - - _placeholderLayer.contents = nil; - _placeholderImage = nil; + // Subclass hook + return nil; } -- (void)recursivelyClearContents +- (BOOL)placeholderShouldPersist { - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node clearContents]; - }); + // Subclass hook + return NO; } -- (void)fetchData +#pragma mark - Hierarchy State + +- (BOOL)isInHierarchy { - // subclass override + ASDN::MutexLocker l(__instanceLock__); + return _flags.isInHierarchy; } -- (void)setNeedsDataFetch +- (void)__enterHierarchy { - if (self.isInPreloadState) { - [self recursivelyFetchData]; + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"Should not cause recursive __enterHierarchy"); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + ASDisplayNodeLogEvent(self, @"enterHierarchy"); + + // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. + __instanceLock__.lock(); + + if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { + _flags.isEnteringHierarchy = YES; + _flags.isInHierarchy = YES; + + // Don't call -willEnterHierarchy while holding __instanceLock__. + // This method and subsequent ones (i.e -interfaceState and didEnter(.*)State) + // don't expect that they are called while the lock is being held. + // More importantly, didEnter(.*)State methods are meant to be overriden by clients. + // And so they can potentially walk up the node tree and cause deadlocks, or do expensive tasks and cause the lock to be held for too long. + __instanceLock__.unlock(); + [self willEnterHierarchy]; + for (ASDisplayNode *subnode in self.subnodes) { + [subnode __enterHierarchy]; + } + __instanceLock__.lock(); + + _flags.isEnteringHierarchy = NO; + + // 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 _locked_shouldHavePlaceholderLayer]) { + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + [self _locked_setupPlaceholderLayerIfNeeded]; + _placeholderLayer.opacity = 1.0; + [CATransaction commit]; + [layer addSublayer:_placeholderLayer]; + } + } } + + __instanceLock__.unlock(); } -- (void)recursivelyFetchData +- (void)__exitHierarchy { - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node fetchData]; - }); -} + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"Should not cause recursive __exitHierarchy"); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + ASDisplayNodeLogEvent(self, @"exitHierarchy"); + + // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. + __instanceLock__.lock(); + + if (_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { + _flags.isExitingHierarchy = YES; + _flags.isInHierarchy = NO; -- (void)clearFetchedData -{ - // subclass override + [self._locked_asyncLayer cancelAsyncDisplay]; + + // Don't call -didExitHierarchy while holding __instanceLock__. + // This method and subsequent ones (i.e -interfaceState and didExit(.*)State) + // don't expect that they are called while the lock is being held. + // More importantly, didExit(.*)State methods are meant to be overriden by clients. + // And so they can potentially walk up the node tree and cause deadlocks, or do expensive tasks and cause the lock to be held for too long. + __instanceLock__.unlock(); + [self didExitHierarchy]; + for (ASDisplayNode *subnode in self.subnodes) { + [subnode __exitHierarchy]; + } + __instanceLock__.lock(); + + _flags.isExitingHierarchy = NO; + } + + __instanceLock__.unlock(); } -- (void)recursivelyClearFetchedData +- (void)enterHierarchyState:(ASHierarchyState)hierarchyState { - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node clearFetchedData]; + if (hierarchyState == ASHierarchyStateNormal) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } + + ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) { + node.hierarchyState |= hierarchyState; }); } -- (void)didEnterVisibleState +- (void)exitHierarchyState:(ASHierarchyState)hierarchyState { - // subclass override + if (hierarchyState == ASHierarchyStateNormal) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } + ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) { + node.hierarchyState &= (~hierarchyState); + }); } -- (void)didExitVisibleState +- (ASHierarchyState)hierarchyState { - // subclass override + ASDN::MutexLocker l(__instanceLock__); + return _hierarchyState; } -- (void)didEnterDisplayState +- (void)setHierarchyState:(ASHierarchyState)newState { - // subclass override -} + ASHierarchyState oldState = ASHierarchyStateNormal; + { + ASDN::MutexLocker l(__instanceLock__); + if (_hierarchyState == newState) { + return; + } + oldState = _hierarchyState; + _hierarchyState = newState; + } + + // Entered rasterization state. + if (newState & ASHierarchyStateRasterized) { + ASDisplayNodeAssert(_flags.synchronous == NO, @"Node created using -initWithViewBlock:/-initWithLayerBlock: cannot be added to subtree of node with shouldRasterizeDescendants=YES. Node: %@", self); + } + + // Entered or exited range managed state. + if ((newState & ASHierarchyStateRangeManaged) != (oldState & ASHierarchyStateRangeManaged)) { + if (newState & ASHierarchyStateRangeManaged) { + [self enterInterfaceState:self.supernode.interfaceState]; + } else { + // The case of exiting a range-managed state should be fairly rare. Adding or removing the node + // to a view hierarchy will cause its interfaceState to be either fully set or unset (all fields), + // but because we might be about to be added to a view hierarchy, exiting the interface state now + // would cause inefficient churn. The tradeoff is that we may not clear contents / fetched data + // for nodes that are removed from a managed state and then retained but not used (bad idea anyway!) + } + } + + if ((newState & ASHierarchyStateLayoutPending) != (oldState & ASHierarchyStateLayoutPending)) { + if (newState & ASHierarchyStateLayoutPending) { + // Entering layout pending state + } else { + // Leaving layout pending state, reset related properties + ASDN::MutexLocker l(__instanceLock__); + _pendingTransitionID = ASLayoutElementContextInvalidTransitionID; + _pendingLayoutTransition = nil; + } + } -- (void)didExitDisplayState -{ - // subclass override + ASDisplayNodeLogEvent(self, @"setHierarchyState: oldState = %@, newState = %@", NSStringFromASHierarchyState(oldState), NSStringFromASHierarchyState(newState)); } -- (void)didEnterPreloadState +- (void)willEnterHierarchy { - [self fetchData]; + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(_flags.isEnteringHierarchy, @"You should never call -willEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); + ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + + if (![self supportsRangeManagedInterfaceState]) { + self.interfaceState = ASInterfaceStateInHierarchy; + } } -- (void)didExitPreloadState +- (void)didExitHierarchy { - if ([self supportsRangeManagedInterfaceState]) { - [self clearFetchedData]; + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode"); + ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + + if (![self supportsRangeManagedInterfaceState]) { + self.interfaceState = ASInterfaceStateNone; + } else { + // This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange: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(self.interfaceState)) { + dispatch_async(dispatch_get_main_queue(), ^{ + // This block intentionally retains self. + __instanceLock__.lock(); + unsigned isInHierarchy = _flags.isInHierarchy; + BOOL isVisible = ASInterfaceStateIncludesVisible(_interfaceState); + ASInterfaceState newState = (_interfaceState & ~ASInterfaceStateVisible); + __instanceLock__.unlock(); + + if (!isInHierarchy && isVisible) { + self.interfaceState = newState; + } + }); + } } } +#pragma mark - Interface State + /** * We currently only set interface state on nodes in table/collection views. For other nodes, if they are * in the hierarchy we enable all ASInterfaceState types with `ASInterfaceStateInHierarchy`, otherwise `None`. */ - (BOOL)supportsRangeManagedInterfaceState { + ASDN::MutexLocker l(__instanceLock__); return ASHierarchyStateIncludesRangeManaged(_hierarchyState); } -- (BOOL)isVisible +- (void)enterInterfaceState:(ASInterfaceState)interfaceState { - ASDN::MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesVisible(_interfaceState); + if (interfaceState == ASInterfaceStateNone) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } + ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { + node.interfaceState |= interfaceState; + }); } -- (BOOL)isInDisplayState +- (void)exitInterfaceState:(ASInterfaceState)interfaceState { - ASDN::MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesDisplay(_interfaceState); + if (interfaceState == ASInterfaceStateNone) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } + ASDisplayNodeLogEvent(self, @"%@ %@", NSStringFromSelector(_cmd), NSStringFromASInterfaceState(interfaceState)); + ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { + node.interfaceState &= (~interfaceState); + }); } -- (BOOL)isInPreloadState +- (void)recursivelySetInterfaceState:(ASInterfaceState)newInterfaceState { - ASDN::MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesPreload(_interfaceState); + // Instead of each node in the recursion assuming it needs to schedule itself for display, + // setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set). + // If our range manager intends for us to be displayed right now, and didn't before, get started! + BOOL shouldScheduleDisplay = [self supportsRangeManagedInterfaceState] && [self shouldScheduleDisplayWithNewInterfaceState:newInterfaceState]; + ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { + node.interfaceState = newInterfaceState; + }); + if (shouldScheduleDisplay) { + [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; + } } - (ASInterfaceState)interfaceState @@ -3020,6 +3528,9 @@ - (void)setInterfaceState:(ASInterfaceState)newState ASDisplayNodeAssertMainThread(); // It should never be possible for a node to be visible but not be allowed / expected to display. ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState)); + // This method manages __instanceLock__ itself, to ensure the lock is not held while didEnter/Exit(.*)State methods are called, thus avoid potential deadlocks + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + ASInterfaceState oldState = ASInterfaceStateNone; { ASDN::MutexLocker l(__instanceLock__); @@ -3029,12 +3540,12 @@ - (void)setInterfaceState:(ASInterfaceState)newState oldState = _interfaceState; _interfaceState = newState; } + + // TODO: Trigger asynchronous measurement if it is not already cached or being calculated. + // if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { + // } - if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { - // Trigger asynchronous measurement if it is not already cached or being calculated. - } - - // For the FetchData and Display ranges, we don't want to call -clear* if not being managed by a range controller. + // For the Preload and Display ranges, we don't want to call -clear* if not being managed by a range controller. // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. // Still, the interfaceState should be updated to the current state of the node; just don't act on the transition. @@ -3046,10 +3557,14 @@ - (void)setInterfaceState:(ASInterfaceState)newState if (nowPreload) { [self didEnterPreloadState]; } else { - [self didExitPreloadState]; + // We don't want to call -didExitPreloadState on nodes that aren't being managed by a range controller. + // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. + if ([self supportsRangeManagedInterfaceState]) { + [self didExitPreloadState]; + } } } - + // Entered or exited contents rendering state. BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState); BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState); @@ -3073,8 +3588,8 @@ - (void)setInterfaceState:(ASInterfaceState)newState // NOTE: This case isn't currently supported as setInterfaceState: isn't exposed externally, and all // internal use cases are range-managed. When a node is visible, don't mess with display - CA will start it. if (!ASInterfaceStateIncludesVisible(newState)) { - // Check __implementsDisplay purely for efficiency - it's faster even than calling -asyncLayer. - if ([self __implementsDisplay]) { + // Check _implementsDisplay purely for efficiency - it's faster even than calling -asyncLayer. + if ([self _implementsDisplay]) { if (nowDisplay) { [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; } else { @@ -3117,228 +3632,183 @@ - (void)setInterfaceState:(ASInterfaceState)newState - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState { - // subclass hook + // Subclass hook + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + [_interfaceStateDelegate interfaceStateDidChange:newState fromState:oldState]; } -- (void)enterInterfaceState:(ASInterfaceState)interfaceState +- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState { - if (interfaceState == ASInterfaceStateNone) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState |= interfaceState; - }); + BOOL willDisplay = ASInterfaceStateIncludesDisplay(newInterfaceState); + BOOL nowDisplay = ASInterfaceStateIncludesDisplay(self.interfaceState); + return willDisplay && (willDisplay != nowDisplay); } -- (void)exitInterfaceState:(ASInterfaceState)interfaceState +- (BOOL)isVisible { - if (interfaceState == ASInterfaceStateNone) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodeLogEvent(self, @"%@ %@", NSStringFromSelector(_cmd), NSStringFromASInterfaceState(interfaceState)); - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState &= (~interfaceState); - }); + ASDN::MutexLocker l(__instanceLock__); + return ASInterfaceStateIncludesVisible(_interfaceState); } -- (void)recursivelySetInterfaceState:(ASInterfaceState)newInterfaceState +- (void)didEnterVisibleState { - // Instead of each node in the recursion assuming it needs to schedule itself for display, - // setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set). - // If our range manager intends for us to be displayed right now, and didn't before, get started! - BOOL shouldScheduleDisplay = [self supportsRangeManagedInterfaceState] && [self shouldScheduleDisplayWithNewInterfaceState:newInterfaceState]; - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState = newInterfaceState; - }); - if (shouldScheduleDisplay) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } + // subclass override + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + [_interfaceStateDelegate didEnterVisibleState]; } -- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState +- (void)didExitVisibleState { - BOOL willDisplay = ASInterfaceStateIncludesDisplay(newInterfaceState); - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(self.interfaceState); - return willDisplay && (willDisplay != nowDisplay); + // subclass override + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + [_interfaceStateDelegate didExitVisibleState]; } -- (ASHierarchyState)hierarchyState +- (BOOL)isInDisplayState { ASDN::MutexLocker l(__instanceLock__); - return _hierarchyState; + return ASInterfaceStateIncludesDisplay(_interfaceState); } -- (void)setHierarchyState:(ASHierarchyState)newState +- (void)didEnterDisplayState { - ASHierarchyState oldState = ASHierarchyStateNormal; - { - ASDN::MutexLocker l(__instanceLock__); - if (_hierarchyState == newState) { - return; - } - oldState = _hierarchyState; - _hierarchyState = newState; - } - - // Entered rasterization state. - if (newState & ASHierarchyStateRasterized) { - ASDisplayNodeAssert(_flags.synchronous == NO, @"Node created using -initWithViewBlock:/-initWithLayerBlock: cannot be added to subtree of node with shouldRasterizeDescendants=YES. Node: %@", self); - } - - // Entered or exited contents rendering state. - if ((newState & ASHierarchyStateRangeManaged) != (oldState & ASHierarchyStateRangeManaged)) { - if (newState & ASHierarchyStateRangeManaged) { - [self enterInterfaceState:self.supernode.interfaceState]; - } else { - // The case of exiting a range-managed state should be fairly rare. Adding or removing the node - // to a view hierarchy will cause its interfaceState to be either fully set or unset (all fields), - // but because we might be about to be added to a view hierarchy, exiting the interface state now - // would cause inefficient churn. The tradeoff is that we may not clear contents / fetched data - // for nodes that are removed from a managed state and then retained but not used (bad idea anyway!) - } - } - - if ((newState & ASHierarchyStateLayoutPending) != (oldState & ASHierarchyStateLayoutPending)) { - if (newState & ASHierarchyStateLayoutPending) { - // Entering layout pending state - } else { - // Leaving layout pending state, reset related properties - { - ASDN::MutexLocker l(__instanceLock__); - _pendingTransitionID = ASLayoutElementContextInvalidTransitionID; - _pendingLayoutTransition = nil; - } - } - } - - ASDisplayNodeLogEvent(self, @"setHierarchyState: oldState = %@, newState = %@", NSStringFromASHierarchyState(oldState), NSStringFromASHierarchyState(newState)); + // subclass override + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + [_interfaceStateDelegate didEnterDisplayState]; } -- (void)enterHierarchyState:(ASHierarchyState)hierarchyState +- (void)didExitDisplayState { - if (hierarchyState == ASHierarchyStateNormal) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - - ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) { - node.hierarchyState |= hierarchyState; - }); + // subclass override + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + [_interfaceStateDelegate didExitDisplayState]; } -- (void)exitHierarchyState:(ASHierarchyState)hierarchyState +- (BOOL)isInPreloadState { - if (hierarchyState == ASHierarchyStateNormal) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) { - node.hierarchyState &= (~hierarchyState); - }); + ASDN::MutexLocker l(__instanceLock__); + return ASInterfaceStateIncludesPreload(_interfaceState); } -- (void)layout +- (void)setNeedsPreload { - ASDisplayNodeAssertMainThread(); - - if (_calculatedDisplayNodeLayout->isDirty()) { - return; + if (self.isInPreloadState) { + [self recursivelyPreload]; } - - [self __layoutSublayouts]; } -- (void)__layoutSublayouts +- (void)recursivelyPreload { - for (ASLayout *subnodeLayout in _calculatedDisplayNodeLayout->layout.sublayouts) { - ((ASDisplayNode *)subnodeLayout.layoutElement).frame = subnodeLayout.frame; - } + ASPerformBlockOnMainThread(^{ + ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { + [node didEnterPreloadState]; + }); + }); } -#pragma mark - Display - -- (void)displayWillStart {} -- (void)displayWillStartAsynchronously:(BOOL)asynchronously +- (void)recursivelyClearPreloadedData { - [self displayWillStart]; // Subclass override - ASDisplayNodeAssertMainThread(); - - ASDisplayNodeLogEvent(self, @"displayWillStart"); - // in case current node takes longer to display than it's subnodes, treat it as a dependent node - [self _pendingNodeWillDisplay:self]; - - [_supernode subnodeDisplayWillStart:self]; + ASPerformBlockOnMainThread(^{ + ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { + [node didExitPreloadState]; + }); + }); } -- (void)displayDidFinish +- (void)didEnterPreloadState { ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + [_interfaceStateDelegate didEnterPreloadState]; - ASDisplayNodeLogEvent(self, @"displayDidFinish"); - [self _pendingNodeDidDisplay:self]; - - [_supernode subnodeDisplayDidFinish:self]; -} - -- (void)subnodeDisplayWillStart:(ASDisplayNode *)subnode -{ - [self _pendingNodeWillDisplay:subnode]; + // Trigger a layout pass to ensure all subnodes have the correct size to preload their content. + // This is important for image nodes, as well as collection and table nodes. + [self layoutIfNeeded]; + + if (_methodOverrides & ASDisplayNodeMethodOverrideFetchData) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self fetchData]; +#pragma clang diagnostic pop + } } -- (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode +- (void)didExitPreloadState { - [self _pendingNodeDidDisplay:subnode]; + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + [_interfaceStateDelegate didExitPreloadState]; + + if (_methodOverrides & ASDisplayNodeMethodOverrideClearFetchedData) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self clearFetchedData]; +#pragma clang diagnostic pop + } } -- (void)setNeedsDisplayAtScale:(CGFloat)contentsScale +- (void)clearContents { - ASDN::MutexLocker l(__instanceLock__); - if (contentsScale != self.contentsScaleForDisplay) { - self.contentsScaleForDisplay = contentsScale; - [self setNeedsDisplay]; + ASDisplayNodeAssertMainThread(); + if (_flags.canClearContentsOfLayer) { + // No-op if these haven't been created yet, as that guarantees they don't have contents that needs to be released. + _layer.contents = nil; } + + _placeholderLayer.contents = nil; + _placeholderImage = nil; } -- (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale +- (void)recursivelyClearContents { - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - [node setNeedsDisplayAtScale:contentsScale]; + ASPerformBlockOnMainThread(^{ + ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { + [node clearContents]; + }); }); } -- (void)hierarchyDisplayDidFinish -{ - // subclass hook -} + + +#pragma mark - Gesture Recognizing - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - // subclass hook + // Subclass hook } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - // subclass hook + // Subclass hook } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - // subclass hook + // Subclass hook } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - // subclass hook + // Subclass hook } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { // This method is only implemented on UIView on iOS 6+. ASDisplayNodeAssertMainThread(); - - if (!_view) + + // No locking needed as it's main thread only + UIView *view = _view; + if (view == nil) { return YES; + } // If we reach the base implementation, forward up the view hierarchy. - UIView *superview = _view.superview; + UIView *superview = view.superview; return [superview gestureRecognizerShouldBegin:gestureRecognizer]; } @@ -3375,23 +3845,64 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event #pragma mark - Pending View State -- (void)_applyPendingStateToViewOrLayer +- (void)_locked_applyPendingStateToViewOrLayer { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(self.nodeLoaded, @"must have a view or layer"); + TIME_SCOPED(_debugTimeToApplyPendingState); + // If no view/layer properties were set before the view/layer were created, _pendingViewState will be nil and the default values // for the view/layer are still valid. + [self _locked_applyPendingViewState]; + + if (_flags.displaySuspended) { + self._locked_asyncLayer.displaySuspended = YES; + } + if (!_flags.displaysAsynchronously) { + self._locked_asyncLayer.displaysAsynchronously = NO; + } +} + +- (void)applyPendingViewState +{ + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + ASDN::MutexLocker l(__instanceLock__); + // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout + // but automatic subnode management would require us to modify the node tree + // in the background on a loaded node, which isn't currently supported. + if (_pendingViewState.hasSetNeedsLayout) { + // Need to unlock before calling setNeedsLayout to avoid deadlocks. + // MutexUnlocker will re-lock at the end of scope. + ASDN::MutexUnlocker u(__instanceLock__); + [self __setNeedsLayout]; + } + + [self _locked_applyPendingViewState]; +} - [self applyPendingViewState]; +- (void)_locked_applyPendingViewState +{ + ASDisplayNodeAssertMainThread(); - // TODO: move this into real pending state - if (_flags.displaySuspended) { - self.asyncLayer.displaySuspended = YES; + if (_flags.layerBacked) { + [_pendingViewState applyToLayer:self.layer]; + } else { + BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(_flags); + [_pendingViewState applyToView:self.view withSpecialPropertiesHandling:specialPropertiesHandling]; } - if (!_flags.displaysAsynchronously) { - self.asyncLayer.displaysAsynchronously = NO; + + // _ASPendingState objects can add up very quickly when adding + // many nodes. This is especially an issue in large collection views + // and table views. This needs to be weighed against the cost of + // reallocing a _ASPendingState. So in range managed nodes we + // delete the pending state, otherwise we just clear it. + if (ASHierarchyStateIncludesRangeManaged(_hierarchyState)) { + _pendingViewState = nil; + } else { + [_pendingViewState clearChanges]; } } @@ -3423,130 +3934,37 @@ - (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy: return nil; } -- (void)recursivelySetDisplaySuspended:(BOOL)flag -{ - _recursivelySetDisplaySuspended(self, nil, flag); -} - -// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or a variant with a condition / test block. -static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, BOOL flag) -{ - // If there is no layer, but node whose its view is loaded, then we can traverse down its layer hierarchy. Otherwise we must stick to the node hierarchy to avoid loading views prematurely. Note that for nodes that haven't loaded their views, they can't possibly have subviews/sublayers, so we don't need to traverse the layer hierarchy for them. - if (!layer && node && node.nodeLoaded) { - layer = node.layer; - } - - // If we don't know the node, but the layer is an async layer, get the node from the layer. - if (!node && layer && [layer isKindOfClass:[_ASDisplayLayer class]]) { - node = layer.asyncdisplaykit_node; - } - - // Set the flag on the node. If this is a pure layer (no node) then this has no effect (plain layers don't support preventing/cancelling display). - node.displaySuspended = flag; - - if (layer && !node.shouldRasterizeDescendants) { - // If there is a layer, recurse down the layer hierarchy to set the flag on descendants. This will cover both layer-based and node-based children. - for (CALayer *sublayer in layer.sublayers) { - _recursivelySetDisplaySuspended(nil, sublayer, flag); - } - } else { - // If there is no layer (view not loaded yet) or this node rasterizes descendants (there won't be a layer tree to traverse), recurse down the subnode hierarchy to set the flag on descendants. This covers only node-based children, but for a node whose view is not loaded it can't possibly have nodeless children. - for (ASDisplayNode *subnode in node.subnodes) { - _recursivelySetDisplaySuspended(subnode, nil, flag); - } - } -} - -- (BOOL)displaySuspended -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.displaySuspended; -} - -- (void)setDisplaySuspended:(BOOL)flag -{ - ASDisplayNodeAssertThreadAffinity(self); - - // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel) - if (_flags.synchronous) - return; - - ASDN::MutexLocker l(__instanceLock__); - - if (_flags.displaySuspended == flag) - return; - - _flags.displaySuspended = flag; - - self.asyncLayer.displaySuspended = flag; - - if ([self __implementsDisplay]) { - // Display start and finish methods needs to happen on the main thread - ASPerformBlockOnMainThread(^{ - if (flag) { - [_supernode subnodeDisplayDidFinish:self]; - } else { - [_supernode subnodeDisplayWillStart:self]; - } - }); - } -} +#pragma mark - Performance Measurement -- (BOOL)shouldAnimateSizeChanges +- (void)setMeasurementOptions:(ASDisplayNodePerformanceMeasurementOptions)measurementOptions { ASDN::MutexLocker l(__instanceLock__); - return _flags.shouldAnimateSizeChanges; + _measurementOptions = measurementOptions; } -- (void)setShouldAnimateSizeChanges:(BOOL)shouldAnimateSizeChanges +- (ASDisplayNodePerformanceMeasurementOptions)measurementOptions { ASDN::MutexLocker l(__instanceLock__); - _flags.shouldAnimateSizeChanges = shouldAnimateSizeChanges; + return _measurementOptions; } -static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; - -- (void)setDrawingPriority:(NSInteger)drawingPriority +- (ASDisplayNodePerformanceMeasurements)performanceMeasurements { - ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(__instanceLock__); - if (drawingPriority == ASDefaultDrawingPriority) { - _flags.hasCustomDrawingPriority = NO; - objc_setAssociatedObject(self, ASDisplayNodeDrawingPriorityKey, nil, OBJC_ASSOCIATION_ASSIGN); - } else { - _flags.hasCustomDrawingPriority = YES; - objc_setAssociatedObject(self, ASDisplayNodeDrawingPriorityKey, @(drawingPriority), OBJC_ASSOCIATION_RETAIN); + ASDisplayNodePerformanceMeasurements measurements = { .layoutSpecNumberOfPasses = -1, .layoutSpecTotalTime = NAN, .layoutComputationNumberOfPasses = -1, .layoutComputationTotalTime = NAN }; + if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec) { + measurements.layoutSpecNumberOfPasses = _layoutSpecNumberOfPasses; + measurements.layoutSpecTotalTime = _layoutSpecTotalTime; } + if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation) { + measurements.layoutComputationNumberOfPasses = _layoutComputationNumberOfPasses; + measurements.layoutComputationTotalTime = _layoutComputationTotalTime; + } + return measurements; } -- (NSInteger)drawingPriority -{ - ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(__instanceLock__); - if (!_flags.hasCustomDrawingPriority) - return ASDefaultDrawingPriority; - else - return [objc_getAssociatedObject(self, ASDisplayNodeDrawingPriorityKey) integerValue]; -} - -- (BOOL)isInHierarchy -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.isInHierarchy; -} - -- (void)setInHierarchy:(BOOL)inHierarchy -{ - ASDN::MutexLocker l(__instanceLock__); - _flags.isInHierarchy = inHierarchy; -} - -- (id)finalLayoutElement -{ - return self; -} -#pragma mark Debugging (Private) +#pragma mark - Debugging (Private) #if ASEVENTLOG_ENABLE - (ASEventLog *)eventLog @@ -3577,16 +3995,33 @@ - (ASEventLog *)eventLog [result addObject:@{ @"frameInWindow" : [NSValue valueWithCGRect:windowFrame] }]; } + // Attempt to find view controller. + // Note that the convenience method asdk_associatedViewController has an assertion + // that it's run on main. Since this is a debug method, let's bypass the assertion + // and run up the chain ourselves. + if (_view != nil) { + for (UIResponder *responder in [_view asdk_responderChainEnumerator]) { + UIViewController *vc = ASDynamicCast(responder, UIViewController); + if (vc) { + [result addObject:@{ @"viewController" : ASObjectDescriptionMakeTiny(vc) }]; + break; + } + } + } + if (_view != nil) { + [result addObject:@{ @"alpha" : @(_view.alpha) }]; [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_view.frame] }]; } else if (_layer != nil) { + [result addObject:@{ @"alpha" : @(_layer.opacity) }]; [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_layer.frame] }]; } else if (_pendingViewState != nil) { + [result addObject:@{ @"alpha" : @(_pendingViewState.alpha) }]; [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_pendingViewState.frame] }]; } // Check supernode so that if we are cell node we don't find self. - ASCellNode *cellNode = ASDisplayNodeFindFirstSupernodeOfClass([self _deallocSafeSupernode], [ASCellNode class]); + ASCellNode *cellNode = ASDisplayNodeFindFirstSupernodeOfClass(self.supernode, [ASCellNode class]); if (cellNode != nil) { [result addObject:@{ @"cellNode" : ASObjectDescriptionMakeTiny(cellNode) }]; } @@ -3606,6 +4041,11 @@ - (ASEventLog *)eventLog } else if (_layerBlock != nil) { [result addObject:@{ @"layerBlock" : _layerBlock }]; } + +#if TIME_DISPLAYNODE_OPS + NSString *creationTypeString = [NSString stringWithFormat:@"cr8:%.2lfms dl:%.2lfms ap:%.2lfms ad:%.2lfms", 1000 * _debugTimeToCreateView, 1000 * _debugTimeForDidLoad, 1000 * _debugTimeToApplyPendingState, 1000 * _debugTimeToAddSubnodeViews]; + [result addObject:@{ @"creationTypeString" : creationTypeString }]; +#endif return result; } @@ -3630,7 +4070,7 @@ - (CGRect)_frameInWindow if (self.layerBacked) { CALayer *rootLayer = _layer; - CALayer *nextLayer = rootLayer; + CALayer *nextLayer = nil; while ((nextLayer = rootLayer.superlayer) != nil) { rootLayer = nextLayer; } @@ -3648,56 +4088,33 @@ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state object return [self.subnodes countByEnumeratingWithState:state objects:buffer count:len]; } -#pragma mark - ASEnvironment - -- (ASEnvironmentState)environmentState -{ - return _environmentState; -} - -- (void)setEnvironmentState:(ASEnvironmentState)environmentState -{ - ASEnvironmentTraitCollection oldTraitCollection = _environmentState.environmentTraitCollection; - _environmentState = environmentState; - - if (ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(oldTraitCollection, _environmentState.environmentTraitCollection) == NO) { - [self asyncTraitCollectionDidChange]; - } -} - -- (ASDisplayNode *)parent -{ - return self.supernode; -} - -- (NSArray *)children -{ - return self.subnodes; -} - -- (BOOL)supportsTraitsCollectionPropagation -{ - return ASEnvironmentStateTraitCollectionPropagationEnabled(); -} +#pragma mark - ASPrimitiveTraitCollection -- (ASEnvironmentTraitCollection)environmentTraitCollection +- (ASPrimitiveTraitCollection)primitiveTraitCollection { - return _environmentState.environmentTraitCollection; + ASDN::MutexLocker l(__instanceLock__); + return _primitiveTraitCollection; } -- (void)setEnvironmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection +- (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection { - if (ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(environmentTraitCollection, _environmentState.environmentTraitCollection) == NO) { - _environmentState.environmentTraitCollection = environmentTraitCollection; - ASDisplayNodeLogEvent(self, @"asyncTraitCollectionDidChange: %@", NSStringFromASEnvironmentTraitCollection(environmentTraitCollection)); + __instanceLock__.lock(); + if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, _primitiveTraitCollection) == NO) { + _primitiveTraitCollection = traitCollection; + ASDisplayNodeLogEvent(self, @"asyncTraitCollectionDidChange: %@", NSStringFromASPrimitiveTraitCollection(traitCollection)); + __instanceLock__.unlock(); + [self asyncTraitCollectionDidChange]; + return; } + + __instanceLock__.unlock(); } - (ASTraitCollection *)asyncTraitCollection { ASDN::MutexLocker l(__instanceLock__); - return [ASTraitCollection traitCollectionWithASEnvironmentTraitCollection:self.environmentTraitCollection]; + return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection]; } - (void)asyncTraitCollectionDidChange @@ -3705,14 +4122,11 @@ - (void)asyncTraitCollectionDidChange // Subclass override } -#pragma mark - Deprecated +ASPrimitiveTraitCollectionDeprecatedImplementation -- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize -{ - return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max]; -} +#pragma mark - ASLayoutElementStyleExtensibility -ASEnvironmentLayoutExtensibilityForwarding +ASLayoutElementStyleExtensibilityForwarding #if TARGET_OS_TV #pragma mark - UIFocusEnvironment Protocol (tvOS) @@ -3747,20 +4161,31 @@ - (UIView *)preferredFocusedView } #endif -@end +#pragma mark - Deprecated -@implementation ASDisplayNode (Debugging) +// This methods cannot be moved into the category ASDisplayNode (Deprecated). So they need to be declared in ASDisplayNode until removed -- (NSString *)descriptionForRecursiveDescription +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { - NSString *creationTypeString = nil; -#if TIME_DISPLAYNODE_OPS - creationTypeString = [NSString stringWithFormat:@"cr8:%.2lfms dl:%.2lfms ap:%.2lfms ad:%.2lfms", 1000 * _debugTimeToCreateView, 1000 * _debugTimeForDidLoad, 1000 * _debugTimeToApplyPendingState, 1000 * _debugTimeToAddSubnodeViews]; -#endif + return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max]; +} + +- (BOOL)usesImplicitHierarchyManagement +{ + return self.automaticallyManagesSubnodes; +} - return [NSString stringWithFormat:@"<%@ alpha:%.2f isLayerBacked:%d frame:%@ %@>", self.description, self.alpha, self.isLayerBacked, NSStringFromCGRect(self.frame), creationTypeString]; +- (void)setUsesImplicitHierarchyManagement:(BOOL)enabled +{ + self.automaticallyManagesSubnodes = enabled; } +@end + +#pragma mark - ASDisplayNode (Debugging) + +@implementation ASDisplayNode (Debugging) + - (NSString *)displayNodeRecursiveDescription { return [self _recursiveDescriptionHelperWithIndent:@""]; @@ -3768,7 +4193,7 @@ - (NSString *)displayNodeRecursiveDescription - (NSString *)_recursiveDescriptionHelperWithIndent:(NSString *)indent { - NSMutableString *subtree = [[[indent stringByAppendingString: self.descriptionForRecursiveDescription] stringByAppendingString:@"\n"] mutableCopy]; + NSMutableString *subtree = [[[indent stringByAppendingString:self.debugDescription] stringByAppendingString:@"\n"] mutableCopy]; for (ASDisplayNode *n in self.subnodes) { [subtree appendString:[n _recursiveDescriptionHelperWithIndent:[indent stringByAppendingString:@" | "]]]; } @@ -3793,6 +4218,8 @@ - (NSString *)asciiArtName @end +#pragma mark - ASDisplayNode UIKit / CA Categories + // We use associated objects as a last resort if our view is not a _ASDisplayView ie it doesn't have the _node ivar to write to static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; @@ -3841,6 +4268,9 @@ - (void)addSubnode:(ASDisplayNode *)subnode if (selfNode) { [selfNode addSubnode:subnode]; } else { + if (subnode.supernode) { + [subnode removeFromSupernode]; + } [self addSubview:subnode.view]; } } @@ -3856,13 +4286,16 @@ - (void)addSubnode:(ASDisplayNode *)subnode if (selfNode) { [selfNode addSubnode:subnode]; } else { + if (subnode.supernode) { + [subnode removeFromSupernode]; + } [self addSublayer:subnode.layer]; } } @end -#pragma mark - Deprecated +#pragma mark - ASDisplayNode (Deprecated) @implementation ASDisplayNode (Deprecated) @@ -3933,57 +4366,19 @@ - (void)loadStateDidChange:(BOOL)inLoadState } } -- (void)cancelLayoutTransitionsInProgress -{ - [self cancelLayoutTransition]; -} - -- (BOOL)usesImplicitHierarchyManagement -{ - return self.automaticallyManagesSubnodes; -} - -- (void)setUsesImplicitHierarchyManagement:(BOOL)enabled -{ - self.automaticallyManagesSubnodes = enabled; -} - -#pragma mark - ASDisplayNode(LayoutDebugging) - -- (void)setShouldVisualizeLayoutSpecs:(BOOL)shouldVisualizeLayoutSpecs -{ - ASDN::MutexLocker l(__instanceLock__); - if (shouldVisualizeLayoutSpecs != [self shouldVisualizeLayoutSpecs]) { - if (shouldVisualizeLayoutSpecs) { - [self enterHierarchyState:ASHierarchyStateVisualizeLayout]; - } else { - [self exitHierarchyState:ASHierarchyStateVisualizeLayout]; - } - [self setNeedsLayout]; - } -} - -- (BOOL)shouldVisualizeLayoutSpecs +- (void)fetchData { - ASDN::MutexLocker l(__instanceLock__); - return ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); + // subclass override } -- (void)setShouldCacheLayoutSpec:(BOOL)shouldCacheLayoutSpec +- (void)clearFetchedData { - ASDN::MutexLocker l(__instanceLock__); - if (_shouldCacheLayoutSpec != shouldCacheLayoutSpec) { - _shouldCacheLayoutSpec = shouldCacheLayoutSpec; - if (_shouldCacheLayoutSpec == NO) { - _layoutSpec = nil; - } - } + // subclass override } -- (BOOL)shouldCacheLayoutSpec +- (void)cancelLayoutTransitionsInProgress { - ASDN::MutexLocker l(__instanceLock__); - return _shouldCacheLayoutSpec; + [self cancelLayoutTransition]; } @end diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.h b/Source/ASDisplayNodeExtras.h similarity index 89% rename from AsyncDisplayKit/ASDisplayNodeExtras.h rename to Source/ASDisplayNodeExtras.h index e7cbf30192..c8e6c9483a 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.h +++ b/Source/ASDisplayNodeExtras.h @@ -14,6 +14,17 @@ #import #import +/** + * Sets the debugName field for these nodes to the given symbol names, within the domain of "self.class" + * For instance, in `MYButtonNode` if you call `ASSetDebugNames(self.titleNode, _countNode)` the debug names + * for the nodes will be set to `MYButtonNode.titleNode` and `MYButtonNode.countNode`. + */ +#if DEBUG + #define ASSetDebugNames(...) _ASSetDebugNames(self.class, @"" # __VA_ARGS__, __VA_ARGS__, nil) +#else + #define ASSetDebugNames(...) +#endif + /// For deallocation of objects on the main thread across multiple run loops. extern void ASPerformMainThreadDeallocation(_Nullable id object); @@ -114,6 +125,16 @@ extern ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernode(ASDisplayNode * */ extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT; +/** + * Given a layer, find the window it lives in, if any. + */ +extern UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer) AS_WARN_UNUSED_RESULT; + +/** + * Given a layer, find the closest view it lives in, if any. + */ +extern UIView * _Nullable ASFindClosestViewOfLayer(CALayer *layer) AS_WARN_UNUSED_RESULT; + /** * Given two nodes, finds their most immediate common parent. Used for geometry conversion methods. * NOTE: It is an error to try to convert between nodes which do not share a common ancestor. This behavior is @@ -163,6 +184,9 @@ extern UIColor *ASDisplayNodeDefaultTintColor() AS_WARN_UNUSED_RESULT; extern void ASDisplayNodeDisableHierarchyNotifications(ASDisplayNode *node); extern void ASDisplayNodeEnableHierarchyNotifications(ASDisplayNode *node); +// Not to be called directly. +extern void _ASSetDebugNames(Class _Nonnull owningClass, NSString * _Nonnull names, ASDisplayNode * _Nullable object, ...); + ASDISPLAYNODE_EXTERN_C_END NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.mm b/Source/ASDisplayNodeExtras.mm similarity index 84% rename from AsyncDisplayKit/ASDisplayNodeExtras.mm rename to Source/ASDisplayNodeExtras.mm index 97b22578b7..13f8c7b797 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.mm +++ b/Source/ASDisplayNodeExtras.mm @@ -8,12 +8,12 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDisplayNodeExtras.h" -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+FrameworkPrivate.h" +#import +#import +#import #import -#import "ASRunLoopQueue.h" +#import extern void ASPerformMainThreadDeallocation(_Nullable id object) { @@ -24,7 +24,7 @@ extern void ASPerformMainThreadDeallocation(_Nullable id object) static ASRunLoopQueue *queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() andHandler:nil]; + queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:nil]; queue.batchSize = 10; }); if (object != nil) { @@ -32,6 +32,24 @@ extern void ASPerformMainThreadDeallocation(_Nullable id object) } } +extern void _ASSetDebugNames(Class _Nonnull owningClass, NSString * _Nonnull names, ASDisplayNode * _Nullable object, ...) +{ + NSString *owningClassName = NSStringFromClass(owningClass); + NSArray *nameArray = [names componentsSeparatedByString:@", "]; + va_list args; + va_start(args, object); + NSInteger i = 0; + for (ASDisplayNode *node = object; node != nil; node = va_arg(args, id), i++) { + NSMutableString *symbolName = [nameArray[i] mutableCopy]; + // Remove any `self.` or `_` prefix + [symbolName replaceOccurrencesOfString:@"self." withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, symbolName.length)]; + [symbolName replaceOccurrencesOfString:@"_" withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, symbolName.length)]; + node.debugName = [NSString stringWithFormat:@"%@.%@", owningClassName, symbolName]; + } + ASDisplayNodeCAssert(nameArray.count == i, @"Malformed call to ASSetDebugNames: %@", names); + va_end(args); +} + extern ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window) { ASDisplayNodeCAssert(![displayNode isLayerBacked], @"displayNode must not be layer backed as it may have a nil window"); @@ -231,6 +249,27 @@ static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possible return NO; } +extern UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer) +{ + UIView *view = ASFindClosestViewOfLayer(layer); + if (UIWindow *window = ASDynamicCast(view, UIWindow)) { + return window; + } else { + return view.window; + } +} + +extern UIView * _Nullable ASFindClosestViewOfLayer(CALayer *layer) +{ + while (layer != nil) { + if (UIView *view = ASDynamicCast(layer.delegate, UIView)) { + return view; + } + layer = layer.superlayer; + } + return nil; +} + extern ASDisplayNode *ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2) { ASDisplayNode *possibleAncestor = node1; diff --git a/AsyncDisplayKit/ASEditableTextNode.h b/Source/ASEditableTextNode.h similarity index 97% rename from AsyncDisplayKit/ASEditableTextNode.h rename to Source/ASEditableTextNode.h index c9f61d88f7..71d4a89da1 100644 --- a/AsyncDisplayKit/ASEditableTextNode.h +++ b/Source/ASEditableTextNode.h @@ -9,11 +9,12 @@ // #import -#import +#import NS_ASSUME_NONNULL_BEGIN @protocol ASEditableTextNodeDelegate; +@class ASTextKitComponents; /** @abstract Implements a node that supports text editing. @@ -93,16 +94,10 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readwrite) UIEdgeInsets textContainerInset; /** - @abstract properties. + @abstract The maximum number of lines to display. Additional lines will require scrolling. + @default 0 (No limit) */ -@property(nonatomic, readwrite, assign) UITextAutocapitalizationType autocapitalizationType; // default is UITextAutocapitalizationTypeSentences -@property(nonatomic, readwrite, assign) UITextAutocorrectionType autocorrectionType; // default is UITextAutocorrectionTypeDefault -@property(nonatomic, readwrite, assign) UITextSpellCheckingType spellCheckingType; // default is UITextSpellCheckingTypeDefault; -@property(nonatomic, readwrite, assign) UIKeyboardType keyboardType; // default is UIKeyboardTypeDefault -@property(nonatomic, readwrite, assign) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault -@property(nonatomic, readwrite, assign) UIReturnKeyType returnKeyType; // default is UIReturnKeyDefault (See note under UIReturnKeyType enum) -@property(nonatomic, readwrite, assign) BOOL enablesReturnKeyAutomatically; // default is NO (when YES, will automatically disable return key when text widget has zero-length contents, and will automatically enable when text widget has non-zero-length contents) -@property(nonatomic, readwrite, assign, getter=isSecureTextEntry) BOOL secureTextEntry; // default is NO +@property (nonatomic, assign) NSUInteger maximumLinesToDisplay; /** @abstract Indicates whether the receiver's text view is the first responder, and thus has the keyboard visible and is prepared for editing by the user. @@ -125,6 +120,18 @@ NS_ASSUME_NONNULL_BEGIN */ - (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; +/** + @abstract properties. + */ +@property(nonatomic, readwrite, assign) UITextAutocapitalizationType autocapitalizationType; // default is UITextAutocapitalizationTypeSentences +@property(nonatomic, readwrite, assign) UITextAutocorrectionType autocorrectionType; // default is UITextAutocorrectionTypeDefault +@property(nonatomic, readwrite, assign) UITextSpellCheckingType spellCheckingType; // default is UITextSpellCheckingTypeDefault; +@property(nonatomic, readwrite, assign) UIKeyboardType keyboardType; // default is UIKeyboardTypeDefault +@property(nonatomic, readwrite, assign) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault +@property(nonatomic, readwrite, assign) UIReturnKeyType returnKeyType; // default is UIReturnKeyDefault (See note under UIReturnKeyType enum) +@property(nonatomic, readwrite, assign) BOOL enablesReturnKeyAutomatically; // default is NO (when YES, will automatically disable return key when text widget has zero-length contents, and will automatically enable when text widget has non-zero-length contents) +@property(nonatomic, readwrite, assign, getter=isSecureTextEntry) BOOL secureTextEntry; // default is NO + @end @interface ASEditableTextNode (Unavailable) diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/Source/ASEditableTextNode.mm similarity index 97% rename from AsyncDisplayKit/ASEditableTextNode.mm rename to Source/ASEditableTextNode.mm index 4fd2dfdc8f..dfd3b79d57 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/Source/ASEditableTextNode.mm @@ -8,14 +8,16 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASEditableTextNode.h" +#import #import #import -#import "ASDisplayNode+Subclasses.h" -#import "ASEqualityHelpers.h" -#import "ASTextNodeWordKerner.h" +#import +#import +#import +#import +#import /** @abstract Object to hold UITextView's pending UITextInputTraits @@ -238,7 +240,16 @@ - (void)didLoad - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; - CGSize textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width]; + + CGSize textSize; + + if (_maximumLinesToDisplay > 0) { + textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width + forMaxNumberOfLines: _maximumLinesToDisplay]; + } else { + textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width]; + } + CGFloat width = std::ceil(textSize.width + _textContainerInset.left + _textContainerInset.right); CGFloat height = std::ceil(textSize.height + _textContainerInset.top + _textContainerInset.bottom); return CGSizeMake(std::fmin(width, constrainedSize.width), std::fmin(height, constrainedSize.height)); @@ -313,6 +324,12 @@ - (UITextView *)textView return _textKitComponents.textView; } +- (void)setMaximumLinesToDisplay:(NSUInteger)maximumLines +{ + _maximumLinesToDisplay = maximumLines; + [self setNeedsLayout]; +} + #pragma mark - @dynamic typingAttributes; diff --git a/AsyncDisplayKit/ASImageNode+AnimatedImage.mm b/Source/ASImageNode+AnimatedImage.mm similarity index 56% rename from AsyncDisplayKit/ASImageNode+AnimatedImage.mm rename to Source/ASImageNode+AnimatedImage.mm index 2acedc109f..cc1e614543 100644 --- a/AsyncDisplayKit/ASImageNode+AnimatedImage.mm +++ b/Source/ASImageNode+AnimatedImage.mm @@ -10,15 +10,25 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASImageNode.h" +#import -#import "ASAssert.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNodeExtras.h" -#import "ASEqualityHelpers.h" -#import "ASImageNode+AnimatedImagePrivate.h" -#import "ASInternalHelpers.h" -#import "ASWeakProxy.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#define ASAnimatedImageDebug 0 + +@interface ASNetworkImageNode (Private) +- (void)_locked_setDefaultImage:(UIImage *)image; +@end NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes; @@ -29,6 +39,11 @@ @implementation ASImageNode (AnimatedImage) - (void)setAnimatedImage:(id )animatedImage { ASDN::MutexLocker l(_animatedImageLock); + [self _locked_setAnimatedImage:animatedImage]; +} + +- (void)_locked_setAnimatedImage:(id )animatedImage +{ if (ASObjectIsEqual(_animatedImage, animatedImage)) { return; } @@ -39,17 +54,19 @@ - (void)setAnimatedImage:(id )animatedImage __weak ASImageNode *weakSelf = self; if ([animatedImage respondsToSelector:@selector(setCoverImageReadyCallback:)]) { animatedImage.coverImageReadyCallback = ^(UIImage *coverImage) { - [weakSelf coverImageCompleted:coverImage]; + // In this case the lock is already gone we have to call the unlocked version therefore + [weakSelf setCoverImageCompleted:coverImage]; }; } if (animatedImage.playbackReady) { - [self animatedImageFileReady]; + [self _locked_setShouldAnimate:YES]; + } else { + animatedImage.playbackReadyCallback = ^{ + // In this case the lock is already gone we have to call the unlocked version therefore + [self setShouldAnimate:YES]; + }; } - - animatedImage.playbackReadyCallback = ^{ - [weakSelf animatedImageFileReady]; - }; } } @@ -62,14 +79,10 @@ - (void)setAnimatedImage:(id )animatedImage - (void)setAnimatedImagePaused:(BOOL)animatedImagePaused { ASDN::MutexLocker l(_animatedImageLock); + _animatedImagePaused = animatedImagePaused; - ASPerformBlockOnMainThread(^{ - if (animatedImagePaused) { - [self stopAnimating]; - } else { - [self startAnimating]; - } - }); + + [self _locked_setShouldAnimate:!animatedImagePaused]; } - (BOOL)animatedImagePaused @@ -78,18 +91,37 @@ - (BOOL)animatedImagePaused return _animatedImagePaused; } -- (void)coverImageCompleted:(UIImage *)coverImage +- (void)setCoverImageCompleted:(UIImage *)coverImage { - BOOL setCoverImage = YES; - { - ASDN::MutexLocker l(_displayLinkLock); - if (_displayLink != nil && _displayLink.paused == NO) { - setCoverImage = NO; - } - } + ASDN::MutexLocker l(_animatedImageLock); + [self _locked_setCoverImageCompleted:coverImage]; +} + +- (void)_locked_setCoverImageCompleted:(UIImage *)coverImage +{ + _displayLinkLock.lock(); + BOOL setCoverImage = (_displayLink == nil) || _displayLink.paused; + _displayLinkLock.unlock(); if (setCoverImage) { - self.image = coverImage; + [self _locked_setCoverImage:coverImage]; + } +} + +- (void)setCoverImage:(UIImage *)coverImage +{ + ASDN::MutexLocker l(_animatedImageLock); + [self _locked_setCoverImage:coverImage]; +} + +- (void)_locked_setCoverImage:(UIImage *)coverImage +{ + //If we're a network image node, we want to set the default image so + //that it will correctly be restored if it exits the range. + if ([self isKindOfClass:[ASNetworkImageNode class]]) { + [(ASNetworkImageNode *)self _locked_setDefaultImage:coverImage]; + } else { + [self _locked_setImage:coverImage]; } } @@ -114,31 +146,64 @@ - (void)setAnimatedImageRunLoopMode:(NSString *)runLoopMode _animatedImageRunLoopMode = runLoopMode; } -- (void)animatedImageFileReady +- (void)setShouldAnimate:(BOOL)shouldAnimate { - ASPerformBlockOnMainThread(^{ - [self startAnimating]; - }); + ASDN::MutexLocker l(_animatedImageLock); + [self _locked_setShouldAnimate:shouldAnimate]; +} + +- (void)_locked_setShouldAnimate:(BOOL)shouldAnimate +{ + // This test is explicitly done and not ASPerformBlockOnMainThread as this would perform the block immediately + // on main if called on main thread and we have to call methods locked or unlocked based on which thread we are on + if (ASDisplayNodeThreadIsMain()) { + if (shouldAnimate) { + [self _locked_startAnimating]; + } else { + [self _locked_stopAnimating]; + } + } else { + // We have to dispatch to the main thread and call the regular methods as the lock is already gone if the + // block is called + dispatch_async(dispatch_get_main_queue(), ^{ + if (shouldAnimate) { + [self startAnimating]; + } else { + [self stopAnimating]; + } + }); + } } +#pragma mark - Animating + - (void)startAnimating { ASDisplayNodeAssertMainThread(); - if (ASInterfaceStateIncludesVisible(self.interfaceState) == NO) { + + ASDN::MutexLocker l(_animatedImageLock); + [self _locked_startAnimating]; +} + +- (void)_locked_startAnimating +{ + // It should be safe to call self.interfaceState in this case as it will only grab the lock of the superclass + if (!ASInterfaceStateIncludesVisible(self.interfaceState)) { return; } - if (self.animatedImagePaused) { + if (_animatedImagePaused) { return; } - if (self.animatedImage.playbackReady == NO) { + if (_animatedImage.playbackReady == NO) { return; } #if ASAnimatedImageDebug NSLog(@"starting animation: %p", self); #endif + ASDN::MutexLocker l(_displayLinkLock); if (_displayLink == nil) { _playHead = 0; @@ -153,6 +218,16 @@ - (void)startAnimating - (void)stopAnimating { + ASDisplayNodeAssertMainThread(); + + ASDN::MutexLocker l(_animatedImageLock); + [self _locked_stopAnimating]; +} + +- (void)_locked_stopAnimating +{ + ASDisplayNodeAssertMainThread(); + #if ASAnimatedImageDebug NSLog(@"stopping animation: %p", self); #endif @@ -161,16 +236,18 @@ - (void)stopAnimating _displayLink.paused = YES; self.lastDisplayLinkFire = 0; - [self.animatedImage clearAnimatedImageCache]; + [_animatedImage clearAnimatedImageCache]; } +#pragma mark - ASDisplayNode + - (void)didEnterVisibleState { ASDisplayNodeAssertMainThread(); [super didEnterVisibleState]; if (self.animatedImage.coverImageReady) { - self.image = self.animatedImage.coverImage; + [self setCoverImage:self.animatedImage.coverImage]; } [self startAnimating]; } @@ -183,6 +260,8 @@ - (void)didExitVisibleState [self stopAnimating]; } +#pragma mark - Display Link Callbacks + - (void)displayLinkFired:(CADisplayLink *)displayLink { ASDisplayNodeAssertMainThread(); @@ -237,6 +316,8 @@ - (NSUInteger)frameIndexAtPlayHeadPosition:(CFTimeInterval)playHead @end +#pragma mark - ASImageNode(AnimatedImageInvalidation) + @implementation ASImageNode(AnimatedImageInvalidation) - (void)invalidateAnimatedImage diff --git a/AsyncDisplayKit/ASImageNode.h b/Source/ASImageNode.h similarity index 99% rename from AsyncDisplayKit/ASImageNode.h rename to Source/ASImageNode.h index 4c7d1c4f8c..22d7430f5f 100644 --- a/AsyncDisplayKit/ASImageNode.h +++ b/Source/ASImageNode.h @@ -8,13 +8,13 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #import -#import "ASImageProtocols.h" -#import "ASBaseDefines.h" - NS_ASSUME_NONNULL_BEGIN +@protocol ASAnimatedImageProtocol; + /** * Image modification block. Use to transform an image before display. * diff --git a/AsyncDisplayKit/ASImageNode.mm b/Source/ASImageNode.mm similarity index 79% rename from AsyncDisplayKit/ASImageNode.mm rename to Source/ASImageNode.mm index 2678a7e8a9..5a461170f0 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -8,28 +8,29 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASImageNode.h" +#import #import -#import "_ASDisplayLayer.h" -#import "ASAssert.h" -#import "ASDimension.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNodeExtras.h" -#import "ASDisplayNode+Beta.h" -#import "ASLayout.h" -#import "ASTextNode.h" -#import "ASImageNode+AnimatedImagePrivate.h" - -#import "ASImageNode+CGExtras.h" -#import "AsyncDisplayKit+Debug.h" - -#import "ASInternalHelpers.h" -#import "ASEqualityHelpers.h" -#import "ASEqualityHashHelpers.h" -#import "ASWeakMap.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +// TODO: It would be nice to remove this dependency; it's the only subclass using more than +FrameworkSubclasses.h +#import #include @@ -96,14 +97,8 @@ - (NSUInteger)hash // Profiling shows that the work done in UIImage's `hash` is on the order of 0.005ms on an A5 processor // and isn't proportional to the size of the image. [_image hash], - - // TODO: Hashing the floats in a CGRect or CGSize is tricky. Equality of floats is - // fuzzy, but it's a 100% requirement that two equal values must produce an identical hash value. - // Until there's a robust solution for hashing floats, leave all float values out of the hash. - // This may lead to a greater number of isEqual comparisons but does not comprimise correctness. - //AS::hash()(_backingSize), - //AS::hash()(_imageDrawRect), - + ASHashFromCGSize(_backingSize), + ASHashFromCGRect(_imageDrawRect), AS::hash()(_isOpaque), [_backgroundColor hash], AS::hash()((void*)_preContextBlock), @@ -183,17 +178,39 @@ - (void)dealloc [self invalidateAnimatedImage]; } +- (UIImage *)placeholderImage +{ + // FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set. + // This would completely eliminate the memory and performance cost of the backing store. + CGSize size = self.calculatedSize; + if ((size.width * size.height) < CGFLOAT_EPSILON) { + return nil; + } + + ASDN::MutexLocker l(__instanceLock__); + + UIGraphicsBeginImageContext(size); + [self.placeholderColor setFill]; + UIRectFill(CGRectMake(0, 0, size.width, size.height)); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return image; +} + #pragma mark - Layout and Sizing - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - ASDN::MutexLocker l(__instanceLock__); + __instanceLock__.lock(); + UIImage *image = _image; + __instanceLock__.unlock(); - if (_image == nil) { + if (image == nil) { return [super calculateSizeThatFits:constrainedSize]; } - return _image.size; + return image.size; } #pragma mark - Setter / Getter @@ -201,23 +218,34 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize - (void)setImage:(UIImage *)image { ASDN::MutexLocker l(__instanceLock__); - if (!ASObjectIsEqual(_image, image)) { - _image = image; + [self _locked_setImage:image]; +} + +- (void)_locked_setImage:(UIImage *)image +{ + if (ASObjectIsEqual(_image, image)) { + return; + } + + _image = image; + + if (image != nil) { - [self setNeedsLayout]; - if (image) { - [self setNeedsDisplay]; - - if ([ASImageNode shouldShowImageScalingOverlay] && _debugLabelNode == nil) { - ASPerformBlockOnMainThread(^{ - _debugLabelNode = [[ASTextNode alloc] init]; - _debugLabelNode.layerBacked = YES; - [self addSubnode:_debugLabelNode]; - }); - } - } else { - self.contents = nil; + // We explicitly call setNeedsDisplay in this case, although we know setNeedsDisplay will be called with lock held. + // Therefore we have to be careful in methods that are involved with setNeedsDisplay to not run into a deadlock + [self setNeedsDisplay]; + + // For debugging purposes we don't care about locking for now + if ([ASImageNode shouldShowImageScalingOverlay] && _debugLabelNode == nil) { + ASPerformBlockOnMainThread(^{ + _debugLabelNode = [[ASTextNode alloc] init]; + _debugLabelNode.layerBacked = YES; + [self addSubnode:_debugLabelNode]; + }); } + + } else { + self.contents = nil; } } @@ -227,6 +255,11 @@ - (UIImage *)image return _image; } +- (UIImage *)_locked_Image +{ + return _image; +} + - (void)setPlaceholderColor:(UIColor *)placeholderColor { _placeholderColor = placeholderColor; @@ -244,7 +277,7 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer _drawParameter = { .bounds = self.bounds, .opaque = self.opaque, - .contentsScale = _contentsScaleForDisplay, + .contentsScale = self.contentsScaleForDisplay, .backgroundColor = self.backgroundColor, .contentMode = self.contentMode, .cropEnabled = _cropEnabled, @@ -273,34 +306,21 @@ - (UIImage *)displayWithParameters:(id *)parameter isCancelled:(asdisp return nil; } - CGRect drawParameterBounds = CGRectZero; - BOOL forceUpscaling = NO; - CGSize forcedSize = CGSizeZero; - BOOL cropEnabled = YES; - BOOL isOpaque = NO; - UIColor *backgroundColor = nil; - UIViewContentMode contentMode = UIViewContentModeScaleAspectFill; - CGFloat contentsScale = 0.0; - CGRect cropDisplayBounds = CGRectZero; - CGRect cropRect = CGRectZero; - asimagenode_modification_block_t imageModificationBlock; - - { - ASDN::MutexLocker l(__instanceLock__); - ASImageNodeDrawParameters drawParameter = _drawParameter; - - drawParameterBounds = drawParameter.bounds; - forceUpscaling = drawParameter.forceUpscaling; - forcedSize = drawParameter.forcedSize; - cropEnabled = drawParameter.cropEnabled; - isOpaque = drawParameter.opaque; - backgroundColor = drawParameter.backgroundColor; - contentMode = drawParameter.contentMode; - contentsScale = drawParameter.contentsScale; - cropDisplayBounds = drawParameter.cropDisplayBounds; - cropRect = drawParameter.cropRect; - imageModificationBlock = drawParameter.imageModificationBlock; - } + __instanceLock__.lock(); + ASImageNodeDrawParameters drawParameter = _drawParameter; + __instanceLock__.unlock(); + + CGRect drawParameterBounds = drawParameter.bounds; + BOOL forceUpscaling = drawParameter.forceUpscaling; + CGSize forcedSize = drawParameter.forcedSize; + BOOL cropEnabled = drawParameter.cropEnabled; + BOOL isOpaque = drawParameter.opaque; + UIColor *backgroundColor = drawParameter.backgroundColor; + UIViewContentMode contentMode = drawParameter.contentMode; + CGFloat contentsScale = drawParameter.contentsScale; + CGRect cropDisplayBounds = drawParameter.cropDisplayBounds; + CGRect cropRect = drawParameter.cropRect; + asimagenode_modification_block_t imageModificationBlock = drawParameter.imageModificationBlock; BOOL hasValidCropBounds = cropEnabled && !CGRectIsEmpty(cropDisplayBounds); CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : drawParameterBounds); @@ -374,26 +394,30 @@ - (UIImage *)displayWithParameters:(id *)parameter isCancelled:(asdisp return nil; } - ASImageNodeContentsKey *contentsKey = [[ASImageNodeContentsKey alloc] init]; - contentsKey.image = image; - contentsKey.backingSize = backingSize; - contentsKey.imageDrawRect = imageDrawRect; - contentsKey.isOpaque = isOpaque; - contentsKey.backgroundColor = backgroundColor; - contentsKey.preContextBlock = preContextBlock; - contentsKey.postContextBlock = postContextBlock; - contentsKey.imageModificationBlock = imageModificationBlock; + ASImageNodeContentsKey *contentsKey = [[ASImageNodeContentsKey alloc] init]; + contentsKey.image = image; + contentsKey.backingSize = backingSize; + contentsKey.imageDrawRect = imageDrawRect; + contentsKey.isOpaque = isOpaque; + contentsKey.backgroundColor = backgroundColor; + contentsKey.preContextBlock = preContextBlock; + contentsKey.postContextBlock = postContextBlock; + contentsKey.imageModificationBlock = imageModificationBlock; - if (isCancelled()) { - return nil; - } + if (isCancelled()) { + return nil; + } - ASWeakMapEntry *entry = [self.class contentsForkey:contentsKey isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled]; - if (entry == nil) { // If nil, we were cancelled. - return nil; - } + ASWeakMapEntry *entry = [self.class contentsForkey:contentsKey isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled]; + if (entry == nil) { // If nil, we were cancelled. + return nil; + } + + __instanceLock__.lock(); _weakCacheEntry = entry; // Retain so that the entry remains in the weak cache - return entry.value; + __instanceLock__.unlock(); + + return entry.value; } static ASWeakMap *cache = nil; @@ -524,9 +548,11 @@ - (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))display } // Stash the block and call-site queue. We'll invoke it in -displayDidFinish. - ASDN::MutexLocker l(__instanceLock__); - if (_displayCompletionBlock != displayCompletionBlock) { - _displayCompletionBlock = displayCompletionBlock; + { + ASDN::MutexLocker l(__instanceLock__); + if (_displayCompletionBlock != displayCompletionBlock) { + _displayCompletionBlock = displayCompletionBlock; + } } [self setNeedsDisplay]; @@ -536,9 +562,11 @@ - (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))display - (void)clearContents { - [super clearContents]; + [super clearContents]; + __instanceLock__.lock(); _weakCacheEntry = nil; // release contents from the cache. + __instanceLock__.unlock(); } #pragma mark - Cropping @@ -556,16 +584,20 @@ - (void)setCropEnabled:(BOOL)cropEnabled - (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds { - ASDN::MutexLocker l(__instanceLock__); - if (_cropEnabled == cropEnabled) + __instanceLock__.lock(); + if (_cropEnabled == cropEnabled) { + __instanceLock__.unlock(); return; + } _cropEnabled = cropEnabled; _cropDisplayBounds = cropBounds; + + UIImage *image = _image; + __instanceLock__.unlock(); // If we have an image to display, display it, respecting our recrop flag. - if (self.image) - { + if (image != nil) { ASPerformBlockOnMainThread(^{ if (recropImmediately) [self displayImmediately]; @@ -583,11 +615,14 @@ - (CGRect)cropRect - (void)setCropRect:(CGRect)cropRect { - ASDN::MutexLocker l(__instanceLock__); - if (CGRectEqualToRect(_cropRect, cropRect)) - return; + { + ASDN::MutexLocker l(__instanceLock__); + if (CGRectEqualToRect(_cropRect, cropRect)) { + return; + } - _cropRect = cropRect; + _cropRect = cropRect; + } // TODO: this logic needs to be updated to respect cropRect. CGSize boundsSize = self.bounds.size; diff --git a/AsyncDisplayKit/ASMapNode.h b/Source/ASMapNode.h similarity index 95% rename from AsyncDisplayKit/ASMapNode.h rename to Source/ASMapNode.h index 1d9b96c7be..0ab7010c7a 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/Source/ASMapNode.h @@ -14,6 +14,11 @@ NS_ASSUME_NONNULL_BEGIN +/** + * Map Annotation options. + * The default behavior is to ignore the annotations' positions, use the region or options specified instead. + * Swift: to select the default behavior, use []. + */ typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions) { /** The annotations' positions are ignored, use the region or options specified instead. */ diff --git a/AsyncDisplayKit/ASMapNode.mm b/Source/ASMapNode.mm similarity index 96% rename from AsyncDisplayKit/ASMapNode.mm rename to Source/ASMapNode.mm index d188223159..bd6be23e39 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/Source/ASMapNode.mm @@ -8,17 +8,18 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import + #if TARGET_OS_IOS -#import "ASMapNode.h" +#import #import -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNodeExtras.h" -#import "ASInsetLayoutSpec.h" -#import "ASInternalHelpers.h" -#import "ASLayout.h" +#import +#import +#import +#import +#import @interface ASMapNode() { @@ -72,9 +73,9 @@ - (void)setLayerBacked:(BOOL)layerBacked [super setLayerBacked:layerBacked]; } -- (void)fetchData +- (void)didEnterPreloadState { - [super fetchData]; + [super didEnterPreloadState]; ASPerformBlockOnMainThread(^{ if (self.isLiveMap) { [self addLiveMap]; @@ -84,9 +85,9 @@ - (void)fetchData }); } -- (void)clearFetchedData +- (void)didExitPreloadState { - [super clearFetchedData]; + [super didExitPreloadState]; ASPerformBlockOnMainThread(^{ if (self.isLiveMap) { [self removeLiveMap]; diff --git a/AsyncDisplayKit/ASMultiplexImageNode.h b/Source/ASMultiplexImageNode.h similarity index 99% rename from AsyncDisplayKit/ASMultiplexImageNode.h rename to Source/ASMultiplexImageNode.h index 147bbb6b6e..5320a461f4 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.h +++ b/Source/ASMultiplexImageNode.h @@ -8,8 +8,6 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#if TARGET_OS_IOS - #import #import #import @@ -276,5 +274,3 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier #endif NS_ASSUME_NONNULL_END - -#endif diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm similarity index 86% rename from AsyncDisplayKit/ASMultiplexImageNode.mm rename to Source/ASMultiplexImageNode.mm index 011071fe62..e1d9f68551 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -8,28 +8,20 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#if TARGET_OS_IOS - -#import "ASMultiplexImageNode.h" +#import #import -#import "ASAvailability.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASLog.h" -#import "ASPhotosFrameworkImageRequest.h" -#import "ASEqualityHelpers.h" -#import "ASInternalHelpers.h" -#import "ASDisplayNodeExtras.h" - -#if !AS_IOS8_SDK_OR_LATER -#error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK. -#endif +#import +#import +#import +#import +#import +#import -#if PIN_REMOTE_IMAGE -#import "ASPINRemoteImageDownloader.h" +#if AS_PIN_REMOTE_IMAGE +#import #else -#import "ASBasicImageDownloader.h" +#import #endif NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain"; @@ -84,14 +76,11 @@ @interface ASMultiplexImageNode () id _downloadIdentifier; // Properties - ASDN::RecursiveMutex __instanceLock__; BOOL _shouldRenderProgressImages; //set on init only - BOOL _downloaderSupportsNewProtocol; BOOL _downloaderImplementsSetProgress; BOOL _downloaderImplementsSetPriority; - BOOL _cacheSupportsCachedImage; BOOL _cacheSupportsClearing; } @@ -127,7 +116,6 @@ - (void)_loadNextImage; @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. @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; @@ -138,8 +126,6 @@ - (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imag @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. */ - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; @@ -148,8 +134,6 @@ - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL com @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. */ - (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock; #endif @@ -158,8 +142,6 @@ - (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identif @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. */ - (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; @@ -176,16 +158,9 @@ - (instancetype)initWithCache:(id)cache downloader:(id)cache; _downloader = (id)downloader; - ASDisplayNodeAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion:."); - - _downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)]; - - ASDisplayNodeAssert(cache == nil || [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion:"); - _downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; _downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; - - _cacheSupportsCachedImage = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)]; + _cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; _shouldRenderProgressImages = YES; @@ -197,7 +172,7 @@ - (instancetype)initWithCache:(id)cache downloader:(id)delegate { if (_delegate == delegate) @@ -396,7 +385,7 @@ - (void)setImageIdentifiers:(NSArray *)imageIdentifiers _imageIdentifiers = [[NSArray alloc] initWithArray:imageIdentifiers copyItems:YES]; } - [self setNeedsDataFetch]; + [self setNeedsPreload]; } - (void)reloadImageIdentifierSources @@ -520,7 +509,7 @@ - (void)_updateProgressImageBlockOnDownloaderIfNeeded if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { return; } - strongSelf.image = progressImage; + [strongSelf _setImage:progressImage]; }; } [_downloader setProgressImageBlock:progress callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier]; @@ -538,7 +527,7 @@ - (void)_clearImage if (shouldReleaseImageOnBackgroundThread) { ASPerformBackgroundDeallocation(image); } - self.image = nil; + [self _setImage:nil]; } #pragma mark - @@ -687,7 +676,6 @@ - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL com - (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock { - ASDisplayNodeAssert(AS_AT_LEAST_IOS8, @"PhotosKit is unavailable on iOS 7."); ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); ASDisplayNodeAssertNotNil(request, @"request is required"); ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); @@ -768,10 +756,8 @@ - (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identif } }]; }]; - if (AS_AT_LEAST_IOS8) { - // If you don't set this, iOS will sometimes infer NSQualityOfServiceUserInteractive and promote the entire queue to that level, damaging system responsiveness - newImageRequestOp.qualityOfService = NSQualityOfServiceUserInitiated; - } + // If you don't set this, iOS will sometimes infer NSQualityOfServiceUserInteractive and promote the entire queue to that level, damaging system responsiveness + newImageRequestOp.qualityOfService = NSQualityOfServiceUserInitiated; _phImageRequestOperation = newImageRequestOp; [phImageRequestQueue addOperation:newImageRequestOp]; } @@ -783,11 +769,9 @@ - (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imag ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); if (_cache) { - if (_cacheSupportsCachedImage) { - [_cache cachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(id imageContainer) { - completionBlock([imageContainer asdk_image]); - }]; - } + [_cache cachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(id imageContainer) { + completionBlock([imageContainer asdk_image]); + }]; } // If we don't have a cache, just fail immediately. else { @@ -818,29 +802,27 @@ - (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL c // Download! ASPerformBlockOnBackgroundThread(^{ - if (_downloaderSupportsNewProtocol) { - [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL - callbackQueue:dispatch_get_main_queue() - downloadProgress:downloadProgressBlock - 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) - return; - - ASDN::MutexLocker l(_downloadIdentifierLock); - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - - completionBlock([imageContainer asdk_image], error); - - // Delegateify. - if (strongSelf->_delegateFlags.downloadFinish) - [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; - }]]; - } + [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL + callbackQueue:dispatch_get_main_queue() + downloadProgress:downloadProgressBlock + 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) + return; + + ASDN::MutexLocker l(_downloadIdentifierLock); + //Getting a result back for a different download identifier, download must not have been successfully canceled + if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { + return; + } + + completionBlock([imageContainer asdk_image], error); + + // Delegateify. + if (strongSelf->_delegateFlags.downloadFinish) + [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; + }]]; [self _updateProgressImageBlockOnDownloaderIfNeeded]; }); } @@ -867,7 +849,7 @@ - (void)_finishedLoadingImage:(UIImage *)image forIdentifier:(id)imageIdentifier UIImage *previousImage = self.image; self.loadedImageIdentifier = imageIdentifier; - self.image = image; + [self _setImage:image]; if (_delegateFlags.updatedImage) { [_delegate multiplexImageNode:self didUpdateImage:image withIdentifier:imageIdentifier fromImage:previousImage withIdentifier:previousIdentifier]; @@ -881,7 +863,7 @@ - (void)_finishedLoadingImage:(UIImage *)image forIdentifier:(id)imageIdentifier } @end -#if TARGET_OS_IOS + @implementation NSURL (ASPhotosFrameworkURLs) + (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options @@ -894,6 +876,3 @@ + (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSi } @end -#endif - -#endif diff --git a/AsyncDisplayKit/ASNavigationController.h b/Source/ASNavigationController.h similarity index 95% rename from AsyncDisplayKit/ASNavigationController.h rename to Source/ASNavigationController.h index 03f18d068f..3b37889b13 100644 --- a/AsyncDisplayKit/ASNavigationController.h +++ b/Source/ASNavigationController.h @@ -12,7 +12,7 @@ #import -#import "ASVisibilityProtocols.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/ASNavigationController.m b/Source/ASNavigationController.m similarity index 98% rename from AsyncDisplayKit/ASNavigationController.m rename to Source/ASNavigationController.m index 79ae3f4232..4f5c9c3e65 100644 --- a/AsyncDisplayKit/ASNavigationController.m +++ b/Source/ASNavigationController.m @@ -10,7 +10,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASNavigationController.h" +#import @implementation ASNavigationController { diff --git a/AsyncDisplayKit/ASNetworkImageNode.h b/Source/ASNetworkImageNode.h similarity index 71% rename from AsyncDisplayKit/ASNetworkImageNode.h rename to Source/ASNetworkImageNode.h index dddc0c426c..0f984eea37 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.h +++ b/Source/ASNetworkImageNode.h @@ -9,11 +9,10 @@ // #import -#import NS_ASSUME_NONNULL_BEGIN -@protocol ASNetworkImageNodeDelegate; +@protocol ASNetworkImageNodeDelegate, ASImageCacheProtocol, ASImageDownloaderProtocol; /** @@ -26,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN @interface ASNetworkImageNode : ASImageNode /** - * The designated initializer. Cache and Downloader are WEAK references. + * The designated initializer. Cache and Downloader are WEAK references. * * @param cache The object that implements a cache of images for the image node. Weak reference. * @param downloader The object that implements image downloading for the image node. Must not be nil. Weak reference. @@ -38,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithCache:(nullable id)cache downloader:(id)downloader NS_DESIGNATED_INITIALIZER; /** - * Convenience initialiser. + * Convenience initializer. * * @return An ASNetworkImageNode configured to use the NSURLSession-powered ASBasicImageDownloader, and no extra cache. */ @@ -50,14 +49,29 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, weak, readwrite) id delegate; /** - * A placeholder image to display while the URL is loading. + * The image to display. + * + * @discussion By setting an image to the image property the ASNetworkImageNode will act like a plain ASImageNode. + * As soon as the URL is set the ASNetworkImageNode will act like an ASNetworkImageNode and the image property + * will be managed internally. This means the image property will be cleared out and replaced by the placeholder + * () image while loading and the final image after the new image data was downloaded and processed. + * If you want to use a placholder image functionality use the defaultImage property instead. + */ +@property (nullable, nonatomic, strong) UIImage *image; + +/** + * A placeholder image to display while the URL is loading. This is slightly different than placeholderImage in the + * ASDisplayNode superclass as defaultImage will *not* be displayed synchronously. If you wish to have the image + * displayed synchronously, use @c placeholderImage. */ @property (nullable, nonatomic, strong, readwrite) UIImage *defaultImage; /** * The URL of a new image to download and display. * - * @discussion Changing this property will reset the displayed image to a placeholder () while loading. + * @discussion By setting an URL, the image property of this node will be managed internally. This means previously + * directly set images to the image property will be cleared out and replaced by the placeholder () image + * while loading and the final image after the new image data was downloaded and processed. */ @property (nullable, nonatomic, strong, readwrite) NSURL *URL; @@ -65,8 +79,11 @@ NS_ASSUME_NONNULL_BEGIN * Download and display a new image. * * @param URL The URL of a new image to download and display. - * * @param reset Whether to display a placeholder () while loading the new image. + * + * @discussion By setting an URL, the image property of this node will be managed internally. This means previously + * directly set images to the image property will be cleared out and replaced by the placeholder () image + * while loading and the final image after the new image data was downloaded and processed. */ - (void)setURL:(nullable NSURL *)URL resetToDefault:(BOOL)reset; diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm new file mode 100755 index 0000000000..fe1d08b714 --- /dev/null +++ b/Source/ASNetworkImageNode.mm @@ -0,0 +1,756 @@ +// +// ASNetworkImageNode.mm +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#if AS_PIN_REMOTE_IMAGE +#import +#endif + +static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; + +@interface ASNetworkImageNode () +{ + // Only access any of these with __instanceLock__. + __weak id _delegate; + + NSURL *_URL; + UIImage *_defaultImage; + + NSUUID *_cacheUUID; + id _downloadIdentifier; + // The download identifier that we have set a progress block on, if any. + id _downloadIdentifierForProgressBlock; + + BOOL _imageLoaded; + BOOL _imageWasSetExternally; + CGFloat _currentImageQuality; + CGFloat _renderedImageQuality; + BOOL _shouldRenderProgressImages; + + struct { + unsigned int delegateDidStartFetchingData:1; + unsigned int delegateDidFailWithError:1; + unsigned int delegateDidFinishDecoding:1; + unsigned int delegateDidLoadImage:1; + } _delegateFlags; + + + // Immutable and set on init only. We don't need to lock in this case. + __weak id _downloader; + struct { + unsigned int downloaderImplementsSetProgress:1; + unsigned int downloaderImplementsSetPriority:1; + unsigned int downloaderImplementsAnimatedImage:1; + unsigned int downloaderImplementsCancelWithResume:1; + } _downloaderFlags; + + // Immutable and set on init only. We don't need to lock in this case. + __weak id _cache; + struct { + unsigned int cacheSupportsClearing:1; + unsigned int cacheSupportsSynchronousFetch:1; + } _cacheFlags; +} + +@end + +@implementation ASNetworkImageNode + +@dynamic image; + +- (instancetype)initWithCache:(id)cache downloader:(id)downloader +{ + if (!(self = [super init])) + return nil; + + _cache = (id)cache; + _downloader = (id)downloader; + + _downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; + _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; + _downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)]; + _downloaderFlags.downloaderImplementsCancelWithResume = [downloader respondsToSelector:@selector(cancelImageDownloadWithResumePossibilityForIdentifier:)]; + + _cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; + _cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)]; + + _shouldCacheImage = YES; + _shouldRenderProgressImages = YES; + self.shouldBypassEnsureDisplay = YES; + + return self; +} + +- (instancetype)init +{ +#if AS_PIN_REMOTE_IMAGE + return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]]; +#else + return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; +#endif +} + +- (void)dealloc +{ + [self _cancelImageDownloadWithResumePossibility:NO]; +} + +#pragma mark - Public methods -- must lock + +/// Setter for public image property. It has the side effect of setting an internal _imageWasSetExternally that prevents setting an image internally. Setting an image internally should happen with the _setImage: method +- (void)setImage:(UIImage *)image +{ + ASDN::MutexLocker l(__instanceLock__); + [self _locked_setImage:image]; +} + +- (void)_locked_setImage:(UIImage *)image +{ + BOOL imageWasSetExternally = (image != nil); + BOOL shouldCancelAndClear = imageWasSetExternally && (imageWasSetExternally != _imageWasSetExternally); + _imageWasSetExternally = imageWasSetExternally; + if (shouldCancelAndClear) { + ASDisplayNodeAssertNil(_URL, @"Directly setting an image on an ASNetworkImageNode causes it to behave like an ASImageNode instead of an ASNetworkImageNode. If this is what you want, set the URL to nil first."); + _URL = nil; + [self _locked_cancelDownloadAndClearImageWithResumePossibility:NO]; + } + + [self _locked__setImage:image]; +} + +/// Setter for private image property. See @c _locked_setImage why this is needed +- (void)_setImage:(UIImage *)image +{ + ASDN::MutexLocker l(__instanceLock__); + [self _locked__setImage:image]; +} + +- (void)_locked__setImage:(UIImage *)image +{ + [super _locked_setImage:image]; +} + +- (void)setURL:(NSURL *)URL +{ + [self setURL:URL resetToDefault:YES]; +} + +- (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset +{ + { + ASDN::MutexLocker l(__instanceLock__); + + ASDisplayNodeAssert(_imageWasSetExternally == NO, @"Setting a URL to an ASNetworkImageNode after setting an image changes its behavior from an ASImageNode to an ASNetworkImageNode. If this is what you want, set the image to nil first."); + + _imageWasSetExternally = NO; + + if (ASObjectIsEqual(URL, _URL)) { + return; + } + + [self _locked_cancelImageDownloadWithResumePossibility:NO]; + + _imageLoaded = NO; + + _URL = URL; + + BOOL hasURL = (_URL == nil); + if (reset || hasURL) { + [self _locked_setCurrentImageQuality:(hasURL ? 0.0 : 1.0)]; + [self _locked__setImage:_defaultImage]; + } + } + + [self setNeedsPreload]; +} + +- (NSURL *)URL +{ + ASDN::MutexLocker l(__instanceLock__); + return _URL; +} + +- (void)setDefaultImage:(UIImage *)defaultImage +{ + ASDN::MutexLocker l(__instanceLock__); + + [self _locked_setDefaultImage:defaultImage]; +} + +- (void)_locked_setDefaultImage:(UIImage *)defaultImage +{ + if (ASObjectIsEqual(defaultImage, _defaultImage)) { + return; + } + + _defaultImage = defaultImage; + + if (!_imageLoaded) { + [self _locked_setCurrentImageQuality:((_URL == nil) ? 0.0 : 1.0)]; + [self _locked__setImage:defaultImage]; + + } +} + +- (UIImage *)defaultImage +{ + ASDN::MutexLocker l(__instanceLock__); + return _defaultImage; +} + +- (void)setCurrentImageQuality:(CGFloat)currentImageQuality +{ + ASDN::MutexLocker l(__instanceLock__); + _currentImageQuality = currentImageQuality; +} + +- (CGFloat)currentImageQuality +{ + ASDN::MutexLocker l(__instanceLock__); + return _currentImageQuality; +} + +/** + * Always use this methods internally to update the current image quality + * We want to maintain the order that currentImageQuality is set regardless of the calling thread, + * so we always have to dispatch to the main threadto ensure that we queue the operations in the correct order. + * (see comment in displayDidFinish) + */ +- (void)_setCurrentImageQuality:(CGFloat)imageQuality +{ + ASDN::MutexLocker l(__instanceLock__); + [self _locked_setCurrentImageQuality:imageQuality]; +} + +- (void)_locked_setCurrentImageQuality:(CGFloat)imageQuality +{ + dispatch_async(dispatch_get_main_queue(), ^{ + // As the setting of the image quality is dispatched the lock is gone by the time the block is executing. + // Therefore we have to grab the lock again + __instanceLock__.lock(); + _currentImageQuality = imageQuality; + __instanceLock__.unlock(); + }); +} + +- (void)setRenderedImageQuality:(CGFloat)renderedImageQuality +{ + ASDN::MutexLocker l(__instanceLock__); + _renderedImageQuality = renderedImageQuality; +} + +- (CGFloat)renderedImageQuality +{ + ASDN::MutexLocker l(__instanceLock__); + return _renderedImageQuality; +} + +- (void)setDelegate:(id)delegate +{ + ASDN::MutexLocker l(__instanceLock__); + _delegate = delegate; + + _delegateFlags.delegateDidStartFetchingData = [delegate respondsToSelector:@selector(imageNodeDidStartFetchingData:)]; + _delegateFlags.delegateDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)]; + _delegateFlags.delegateDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]; + _delegateFlags.delegateDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)]; +} + +- (id)delegate +{ + ASDN::MutexLocker l(__instanceLock__); + return _delegate; +} + +- (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages +{ + { + ASDN::MutexLocker l(__instanceLock__); + if (shouldRenderProgressImages == _shouldRenderProgressImages) { + return; + } + _shouldRenderProgressImages = shouldRenderProgressImages; + } + + [self _updateProgressImageBlockOnDownloaderIfNeeded]; +} + +- (BOOL)shouldRenderProgressImages +{ + ASDN::MutexLocker l(__instanceLock__); + return _shouldRenderProgressImages; +} + +- (BOOL)placeholderShouldPersist +{ + ASDN::MutexLocker l(__instanceLock__); + return (self.image == nil && _URL != nil); +} + +/* displayWillStart in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary + in ASMultiplexImageNode as well. */ +- (void)displayWillStartAsynchronously:(BOOL)asynchronously +{ + [super displayWillStartAsynchronously:asynchronously]; + + if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) { + ASDN::MutexLocker l(__instanceLock__); + + if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) { + UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image]; + if (result) { + [self _locked_setCurrentImageQuality:1.0]; + [self _locked__setImage:result]; + + _imageLoaded = YES; + } + } + } + + // TODO: Consider removing this; it predates ASInterfaceState, which now ensures that even non-range-managed nodes get a -preload call. + [self didEnterPreloadState]; + + if (self.image == nil && _downloaderFlags.downloaderImplementsSetPriority) { + __instanceLock__.lock(); + id downloadIdentifier = _downloadIdentifier; + __instanceLock__.unlock(); + if (downloadIdentifier != nil) { + [_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:downloadIdentifier]; + } + } +} + +/* visibileStateDidChange in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary + in ASMultiplexImageNode as well. */ +- (void)didEnterVisibleState +{ + [super didEnterVisibleState]; + + __instanceLock__.lock(); + id downloadIdentifier = nil; + if (_downloaderFlags.downloaderImplementsSetPriority) { + downloadIdentifier = _downloadIdentifier; + } + __instanceLock__.unlock(); + + if (downloadIdentifier != nil) { + [_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:downloadIdentifier]; + } + + [self _updateProgressImageBlockOnDownloaderIfNeeded]; +} + +- (void)didExitVisibleState +{ + [super didExitVisibleState]; + + __instanceLock__.lock(); + id downloadIdentifier = nil; + if (_downloaderFlags.downloaderImplementsSetPriority) { + downloadIdentifier = _downloadIdentifier; + } + __instanceLock__.unlock(); + + if (downloadIdentifier != nil) { + [_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:downloadIdentifier]; + } + + [self _updateProgressImageBlockOnDownloaderIfNeeded]; +} + +- (void)didExitPreloadState +{ + [super didExitPreloadState]; + + __instanceLock__.lock(); + BOOL imageWasSetExternally = _imageWasSetExternally; + __instanceLock__.unlock(); + // If the image was set explicitly we don't want to remove it while exiting the preload state + if (imageWasSetExternally) { + return; + } + + [self _cancelDownloadAndClearImageWithResumePossibility:YES]; +} + +- (void)didEnterPreloadState +{ + [super didEnterPreloadState]; + + // Image was set externally no need to load an image + [self _lazilyLoadImageIfNecessary]; +} + +#pragma mark - Progress + +- (void)handleProgressImage:(UIImage *)progressImage progress:(CGFloat)progress downloadIdentifier:(nullable id)downloadIdentifier +{ + __instanceLock__.lock(); + + // Getting a result back for a different download identifier, download must not have been successfully canceled + if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { + return; + } + + [self _locked_setCurrentImageQuality:progress]; + [self _locked__setImage:progressImage]; + + __instanceLock__.unlock(); +} + +- (void)_updateProgressImageBlockOnDownloaderIfNeeded +{ + // If the downloader doesn't do progress, we are done. + if (_downloaderFlags.downloaderImplementsSetProgress == NO) { + return; + } + + // Read state. + __instanceLock__.lock(); + BOOL shouldRender = _shouldRenderProgressImages && ASInterfaceStateIncludesVisible(_interfaceState); + id oldDownloadIDForProgressBlock = _downloadIdentifierForProgressBlock; + id newDownloadIDForProgressBlock = shouldRender ? _downloadIdentifier : nil; + BOOL clearAndReattempt = NO; + __instanceLock__.unlock(); + + // If we're already bound to the correct download, we're done. + if (ASObjectIsEqual(oldDownloadIDForProgressBlock, newDownloadIDForProgressBlock)) { + return; + } + + // Unbind from the previous download. + if (oldDownloadIDForProgressBlock != nil) { + [_downloader setProgressImageBlock:nil callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:oldDownloadIDForProgressBlock]; + } + + // Bind to the current download. + if (newDownloadIDForProgressBlock != nil) { + __weak __typeof(self) weakSelf = self; + [_downloader setProgressImageBlock:^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) { + [weakSelf handleProgressImage:progressImage progress:progress downloadIdentifier:downloadIdentifier]; + } callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:newDownloadIDForProgressBlock]; + } + + // Update state local state with lock held. + { + ASDN::MutexLocker l(__instanceLock__); + // Check if the oldDownloadIDForProgressBlock still is the same as the _downloadIdentifierForProgressBlock + if (_downloadIdentifierForProgressBlock == oldDownloadIDForProgressBlock) { + _downloadIdentifierForProgressBlock = newDownloadIDForProgressBlock; + } else if (newDownloadIDForProgressBlock != nil) { + // If this is not the case another thread did change the _downloadIdentifierForProgressBlock already so + // we have to deregister the newDownloadIDForProgressBlock that we registered above + clearAndReattempt = YES; + } + } + + if (clearAndReattempt) { + // In this case another thread changed the _downloadIdentifierForProgressBlock before we finished registering + // the new progress block for newDownloadIDForProgressBlock ID. Let's clear it now and reattempt to register + if (newDownloadIDForProgressBlock) { + [_downloader setProgressImageBlock:nil callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:newDownloadIDForProgressBlock]; + } + [self _updateProgressImageBlockOnDownloaderIfNeeded]; + } +} + +- (void)_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResume +{ + ASDN::MutexLocker l(__instanceLock__); + [self _locked_cancelDownloadAndClearImageWithResumePossibility:storeResume]; +} + +- (void)_locked_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResume +{ + [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; + + // 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. + UIImage *image = [self _locked_Image]; + UIImage *defaultImage = _defaultImage; + CGSize imageSize = image.size; + BOOL shouldReleaseImageOnBackgroundThread = imageSize.width > kMinReleaseImageOnBackgroundSize.width || + imageSize.height > kMinReleaseImageOnBackgroundSize.height; + if (shouldReleaseImageOnBackgroundThread) { + ASPerformBackgroundDeallocation(image); + } + + [self _locked_setAnimatedImage:nil]; + [self _locked_setCurrentImageQuality:0.0]; + [self _locked__setImage:defaultImage]; + + _imageLoaded = NO; + + if (_cacheFlags.cacheSupportsClearing) { + if (_URL != nil) { + [_cache clearFetchedImageFromCacheWithURL:_URL]; + } + } +} + +- (void)_cancelImageDownloadWithResumePossibility:(BOOL)storeResume +{ + ASDN::MutexLocker l(__instanceLock__); + [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; +} + +- (void)_locked_cancelImageDownloadWithResumePossibility:(BOOL)storeResume +{ + if (!_downloadIdentifier) { + return; + } + + if (_downloadIdentifier) { + if (storeResume && _downloaderFlags.downloaderImplementsCancelWithResume) { + [_downloader cancelImageDownloadWithResumePossibilityForIdentifier:_downloadIdentifier]; + } else { + [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; + } + } + _downloadIdentifier = nil; + + _cacheUUID = nil; +} + +- (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier))finished +{ + ASPerformBlockOnBackgroundThread(^{ + NSURL *url; + id downloadIdentifier; + BOOL cancelAndReattempt = NO; + + // Below, to avoid performance issues, we're calling downloadImageWithURL without holding the lock. This is a bit ugly because + // We need to reobtain the lock after and ensure that the task we've kicked off still matches our URL. If not, we need to cancel + // it and try again. + { + ASDN::MutexLocker l(__instanceLock__); + url = _URL; + } + + downloadIdentifier = [_downloader downloadImageWithURL:url + callbackQueue:dispatch_get_main_queue() + downloadProgress:NULL + completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { + if (finished != NULL) { + finished(imageContainer, error, downloadIdentifier); + } + }]; + + { + ASDN::MutexLocker l(__instanceLock__); + if (ASObjectIsEqual(_URL, url)) { + // The download we kicked off is correct, no need to do any more work. + _downloadIdentifier = downloadIdentifier; + } else { + // The URL changed since we kicked off our download task. This shouldn't happen often so we'll pay the cost and + // cancel that request and kick off a new one. + cancelAndReattempt = YES; + } + } + + if (cancelAndReattempt) { + if (downloadIdentifier != nil) { + [_downloader cancelImageDownloadForIdentifier:downloadIdentifier]; + } + [self _downloadImageWithCompletion:finished]; + return; + } + + [self _updateProgressImageBlockOnDownloaderIfNeeded]; + }); +} + +- (void)_lazilyLoadImageIfNecessary +{ + __instanceLock__.lock(); + __weak id delegate = _delegate; + BOOL delegateDidStartFetchingData = _delegateFlags.delegateDidStartFetchingData; + BOOL isImageLoaded = _imageLoaded; + NSURL *URL = _URL; + id currentDownloadIdentifier = _downloadIdentifier; + __instanceLock__.unlock(); + + if (!isImageLoaded && URL != nil && currentDownloadIdentifier == nil) { + if (delegateDidStartFetchingData) { + [delegate imageNodeDidStartFetchingData:self]; + } + + if (URL.isFileURL) { + dispatch_async(dispatch_get_main_queue(), ^{ + ASDN::MutexLocker l(__instanceLock__); + + // Bail out if not the same URL anymore + if (!ASObjectIsEqual(URL, _URL)) { + return; + } + + if (_shouldCacheImage) { + [self _locked__setImage:[UIImage imageNamed:_URL.path.lastPathComponent]]; + } else { + // First try to load the path directly, for efficiency assuming a developer who + // doesn't want caching is trying to be as minimal as possible. + UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:_URL.path]; + if (nonAnimatedImage == nil) { + // If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the + // extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage. + NSString *filename = [[NSBundle mainBundle] pathForResource:_URL.path.lastPathComponent ofType:nil]; + if (filename != nil) { + nonAnimatedImage = [UIImage imageWithContentsOfFile:filename]; + } + } + + // If the file may be an animated gif and then created an animated image. + id animatedImage = nil; + if (_downloaderFlags.downloaderImplementsAnimatedImage) { + NSData *data = [NSData dataWithContentsOfURL:_URL]; + if (data != nil) { + animatedImage = [_downloader animatedImageWithData:data]; + + if ([animatedImage respondsToSelector:@selector(isDataSupported:)] && [animatedImage isDataSupported:data] == NO) { + animatedImage = nil; + } + } + } + + if (animatedImage != nil) { + [self _locked_setAnimatedImage:animatedImage]; + } else { + [self _locked__setImage:nonAnimatedImage]; + } + } + + _imageLoaded = YES; + + [self _locked_setCurrentImageQuality:1.0]; + + if (_delegateFlags.delegateDidLoadImage) { + ASDN::MutexUnlocker u(__instanceLock__); + [delegate imageNode:self didLoadImage:self.image]; + } + }); + } else { + __weak __typeof__(self) weakSelf = self; + void (^finished)(id , NSError *, id downloadIdentifier) = ^(id imageContainer, NSError *error, id downloadIdentifier) { + + __typeof__(self) strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + + // Grab the lock for the rest of the block + ASDN::MutexLocker l(strongSelf->__instanceLock__); + + //Getting a result back for a different download identifier, download must not have been successfully canceled + if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { + return; + } + + if (imageContainer != nil) { + [strongSelf _locked_setCurrentImageQuality:1.0]; + if ([imageContainer asdk_animatedImageData] && strongSelf->_downloaderFlags.downloaderImplementsAnimatedImage) { + id animatedImage = [strongSelf->_downloader animatedImageWithData:[imageContainer asdk_animatedImageData]]; + [strongSelf _locked_setAnimatedImage:animatedImage]; + } else { + [strongSelf _locked__setImage:[imageContainer asdk_image]]; + } + strongSelf->_imageLoaded = YES; + } + + strongSelf->_downloadIdentifier = nil; + strongSelf->_cacheUUID = nil; + + if (imageContainer != nil) { + if (strongSelf->_delegateFlags.delegateDidLoadImage) { + ASDN::MutexUnlocker u(strongSelf->__instanceLock__); + [delegate imageNode:strongSelf didLoadImage:strongSelf.image]; + } + } else if (error && strongSelf->_delegateFlags.delegateDidFailWithError) { + ASDN::MutexUnlocker u(strongSelf->__instanceLock__); + [delegate imageNode:strongSelf didFailWithError:error]; + } + }; + + // As the _cache and _downloader is only set once in the intializer we don't have to use a + // lock in here + if (_cache != nil) { + NSUUID *cacheUUID = [NSUUID UUID]; + __instanceLock__.lock(); + _cacheUUID = cacheUUID; + __instanceLock__.unlock(); + + [_cache cachedImageWithURL:URL + callbackQueue:dispatch_get_main_queue() + completion:^(id imageContainer) { + // If the cache UUID changed, that means this request was cancelled. + __instanceLock__.lock(); + NSUUID *currentCacheUUID = _cacheUUID; + __instanceLock__.unlock(); + + if (!ASObjectIsEqual(currentCacheUUID, cacheUUID)) { + return; + } + + if ([imageContainer asdk_image] == nil && _downloader != nil) { + [self _downloadImageWithCompletion:finished]; + } else { + finished(imageContainer, nil, nil); + } + }]; + } else { + [self _downloadImageWithCompletion:finished]; + } + } + } +} + +#pragma mark - ASDisplayNode+Subclasses + +- (void)displayDidFinish +{ + [super displayDidFinish]; + + id delegate = nil; + + __instanceLock__.lock(); + if (_delegateFlags.delegateDidFinishDecoding && self.layer.contents != nil) { + /* We store the image quality in _currentImageQuality whenever _image is set. On the following displayDidFinish, we'll know that + _currentImageQuality is the quality of the image that has just finished rendering. In order for this to be accurate, we + need to be sure we are on main thread when we set _currentImageQuality. Otherwise, it is possible for _currentImageQuality + to be modified at a point where it is too late to cancel the main thread's previous display (the final sentinel check has passed), + but before the displayDidFinish of the previous display pass is called. In this situation, displayDidFinish would be called and we + would set _renderedImageQuality to the new _currentImageQuality, but the actual quality of the rendered image should be the previous + value stored in _currentImageQuality. */ + + _renderedImageQuality = _currentImageQuality; + + // Assign the delegate to be used + delegate = _delegate; + } + + __instanceLock__.unlock(); + + if (delegate != nil) { + [delegate imageNodeDidFinishDecoding:self]; + } +} + +@end diff --git a/Source/ASNodeController+Beta.h b/Source/ASNodeController+Beta.h new file mode 100644 index 0000000000..05a859792f --- /dev/null +++ b/Source/ASNodeController+Beta.h @@ -0,0 +1,32 @@ +// +// ASNodeController.h +// AsyncDisplayKit +// +// Created by Hannah Troisi for Scott Goodson on 1/27/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import // for ASInterfaceState protocol + +/* ASNodeController is currently beta and open to change in the future */ +@interface ASNodeController<__covariant DisplayNodeType : ASDisplayNode *> : NSObject + +@property (nonatomic, strong) DisplayNodeType node; + +- (void)loadNode; + +// for descriptions see definition +- (void)didEnterVisibleState ASDISPLAYNODE_REQUIRES_SUPER; +- (void)didExitVisibleState ASDISPLAYNODE_REQUIRES_SUPER; + +- (void)didEnterDisplayState ASDISPLAYNODE_REQUIRES_SUPER; +- (void)didExitDisplayState ASDISPLAYNODE_REQUIRES_SUPER; + +- (void)didEnterPreloadState ASDISPLAYNODE_REQUIRES_SUPER; +- (void)didExitPreloadState ASDISPLAYNODE_REQUIRES_SUPER; + +- (void)interfaceStateDidChange:(ASInterfaceState)newState + fromState:(ASInterfaceState)oldState ASDISPLAYNODE_REQUIRES_SUPER; + +@end diff --git a/Source/ASNodeController+Beta.m b/Source/ASNodeController+Beta.m new file mode 100644 index 0000000000..2fd01497bd --- /dev/null +++ b/Source/ASNodeController+Beta.m @@ -0,0 +1,58 @@ +// +// ASNodeController.mm +// AsyncDisplayKit +// +// Created by Hannah Troisi for Scott Goodson on 1/27/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "ASNodeController+Beta.h" + +#import "ASDisplayNode+FrameworkPrivate.h" + +@implementation ASNodeController + +@synthesize node = _node; + +- (instancetype)init +{ + self = [super init]; + if (self) { + + } + return self; +} + +- (void)loadNode +{ + self.node = [[ASDisplayNode alloc] init]; +} + +- (ASDisplayNode *)node +{ + if (_node == nil) { + [self loadNode]; + } + return _node; +} + +-(void)setNode:(ASDisplayNode *)node +{ + _node = node; + _node.interfaceStateDelegate = self; +} + +// subclass overrides +- (void)didEnterVisibleState {} +- (void)didExitVisibleState {} + +- (void)didEnterDisplayState {} +- (void)didExitDisplayState {} + +- (void)didEnterPreloadState {} +- (void)didExitPreloadState {} + +- (void)interfaceStateDidChange:(ASInterfaceState)newState + fromState:(ASInterfaceState)oldState {} + +@end diff --git a/AsyncDisplayKit/ASPagerFlowLayout.h b/Source/ASPagerFlowLayout.h similarity index 100% rename from AsyncDisplayKit/ASPagerFlowLayout.h rename to Source/ASPagerFlowLayout.h diff --git a/Source/ASPagerFlowLayout.m b/Source/ASPagerFlowLayout.m new file mode 100644 index 0000000000..e934c0a6bc --- /dev/null +++ b/Source/ASPagerFlowLayout.m @@ -0,0 +1,113 @@ +// +// ASPagerFlowLayout.m +// AsyncDisplayKit +// +// Created by Levi McCallum on 2/12/16. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import +#import + +@interface ASPagerFlowLayout () { + __weak ASCellNode *_currentCellNode; +} + +@end + +//TODO make this an ASCollectionViewLayout +@implementation ASPagerFlowLayout + +- (ASCollectionView *)asCollectionView +{ + // Dynamic cast is too slow and not worth it. + return (ASCollectionView *)self.collectionView; +} + +- (void)prepareLayout +{ + [super prepareLayout]; + if (_currentCellNode == nil) { + [self _updateCurrentNode]; + } +} + +- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset +{ + // Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should + // try to use the current index path to not end up setting the target content offset to something in between pages + if (!self.collectionView.decelerating && !self.collectionView.tracking) { + NSIndexPath *indexPath = [self.asCollectionView indexPathForNode:_currentCellNode]; + if (indexPath) { + return [self _targetContentOffsetForItemAtIndexPath:indexPath proposedContentOffset:proposedContentOffset]; + } + } + + return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; +} + +- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset +{ + if ([self _dataSourceIsEmpty]) { + return proposedContentOffset; + } + + UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; + if (attributes == nil) { + return proposedContentOffset; + } + + CGFloat xOffset = (CGRectGetWidth(self.collectionView.bounds) - CGRectGetWidth(attributes.frame)) / 2.0; + return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y); +} + +- (BOOL)_dataSourceIsEmpty +{ + return ([self.collectionView numberOfSections] == 0 || + [self.collectionView numberOfItemsInSection:0] == 0); +} + +- (void)_updateCurrentNode +{ + // Never change node during an animated bounds change (rotation) + // NOTE! Listening for -prepareForAnimatedBoundsChange and -finalizeAnimatedBoundsChange + // isn't sufficient here! It's broken! + NSArray *animKeys = self.collectionView.layer.animationKeys; + for (NSString *key in animKeys) { + if ([key hasPrefix:@"bounds"]) { + return; + } + } + + CGRect bounds = self.collectionView.bounds; + CGRect rect = CGRectMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 1, 1); + + NSIndexPath *indexPath = [self layoutAttributesForElementsInRect:rect].firstObject.indexPath; + if (indexPath) { + ASCellNode *node = [self.asCollectionView nodeForItemAtIndexPath:indexPath]; + if (node) { + _currentCellNode = node; + } + } +} + +- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds +{ + [self _updateCurrentNode]; + return [super shouldInvalidateLayoutForBoundsChange:newBounds]; +} + +- (UICollectionViewLayoutInvalidationContext *)invalidationContextForBoundsChange:(CGRect)newBounds +{ + UICollectionViewFlowLayoutInvalidationContext *ctx = (UICollectionViewFlowLayoutInvalidationContext *)[super invalidationContextForBoundsChange:newBounds]; + ctx.invalidateFlowLayoutDelegateMetrics = YES; + ctx.invalidateFlowLayoutAttributes = YES; + return ctx; +} + +@end diff --git a/AsyncDisplayKit/ASPagerNode.h b/Source/ASPagerNode.h similarity index 77% rename from AsyncDisplayKit/ASPagerNode.h rename to Source/ASPagerNode.h index 1bb790a192..37d3842051 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/Source/ASPagerNode.h @@ -11,7 +11,6 @@ // #import -#import @class ASPagerNode; @class ASPagerFlowLayout; @@ -67,7 +66,7 @@ NS_ASSUME_NONNULL_BEGIN * @param index The index of the node. * @return A constrained size range for layout the node at this index. */ -- (ASSizeRange)pagerNode:(ASPagerNode *)pagerNode constrainedSizeForNodeAtIndex:(NSInteger)index; +- (ASSizeRange)pagerNode:(ASPagerNode *)pagerNode constrainedSizeForNodeAtIndex:(NSInteger)index ASDISPLAYNODE_DEPRECATED_MSG("Pages in a pager node should be the exact size of the collection node (default behavior)."); @end @@ -121,6 +120,24 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSInteger)indexOfPageWithNode:(ASCellNode *)node; +/** + * Tells the pager node to allow its view controller to automatically adjust its content insets. + * + * @see UIViewController.automaticallyAdjustsScrollViewInsets + * + * @discussion ASPagerNode should usually not have its content insets automatically adjusted + * because it scrolls horizontally, and flow layout will log errors because the pages + * do not fit between the top & bottom insets of the collection view. + * + * The default value is NO, which means that ASPagerNode expects that its view controller will + * have automaticallyAdjustsScrollViewInsets=NO. + * + * If this property is NO, but your view controller has automaticallyAdjustsScrollViewInsets=YES, + * the pager node will set the property on the view controller to NO and log a warning message. In the future, + * the pager node will just log the warning, and you'll need to configure your view controller on your own. + */ +@property (nonatomic, assign) BOOL allowsAutomaticInsetsAdjustment; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASPagerNode.m b/Source/ASPagerNode.m similarity index 81% rename from AsyncDisplayKit/ASPagerNode.m rename to Source/ASPagerNode.m index 4e8db5d654..b1c42e8e15 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/Source/ASPagerNode.m @@ -10,15 +10,17 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASPagerNode.h" -#import "ASDelegateProxy.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASPagerFlowLayout.h" -#import "ASAssert.h" -#import "ASCellNode.h" -#import "ASCollectionView+Undeprecated.h" - -@interface ASPagerNode () +#import +#import +#import +#import +#import +#import +#import +#import +#import + +@interface ASPagerNode () { ASPagerFlowLayout *_flowLayout; @@ -80,11 +82,6 @@ - (void)didLoad cv.allowsSelection = NO; cv.showsVerticalScrollIndicator = NO; cv.showsHorizontalScrollIndicator = NO; - - // Zeroing contentInset is important, as UIKit will set the top inset for the navigation bar even though - // our view is only horizontally scrollable. This causes UICollectionViewFlowLayout to log a warning. - // From here we cannot disable this directly (UIViewController's automaticallyAdjustsScrollViewInsets). - cv.zeroContentInsets = YES; ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.0, .trailingBufferScreenfuls = 0.0 }; ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; @@ -153,11 +150,14 @@ - (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSe - (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" if (_pagerDelegateFlags.constrainedSizeForNode) { return [_pagerDelegate pagerNode:self constrainedSizeForNodeAtIndex:indexPath.item]; } +#pragma clang diagnostic pop - return ASSizeRangeMake(CGSizeZero, self.bounds.size); + return ASSizeRangeMake(self.bounds.size); } #pragma mark - Data Source Proxy @@ -208,4 +208,21 @@ - (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy [self setDelegate:nil]; } +- (void)didEnterVisibleState +{ + [super didEnterVisibleState]; + + // Check that our view controller does not automatically set our content insets + // It would be better to have a -didEnterHierarchy hook to put this in, but + // such a hook doesn't currently exist, and in every use case I can imagine, + // the pager is not hosted inside a range-managed node. + if (_allowsAutomaticInsetsAdjustment == NO) { + UIViewController *vc = [self.view asdk_associatedViewController]; + if (vc.automaticallyAdjustsScrollViewInsets) { + NSLog(@"AsyncDisplayKit: ASPagerNode is setting automaticallyAdjustsScrollViewInsets=NO on its owning view controller %@. This automatic behavior will be disabled in the future. Set allowsAutomaticInsetsAdjustment=YES on the pager node to suppress this behavior.", vc); + vc.automaticallyAdjustsScrollViewInsets = NO; + } + } +} + @end diff --git a/AsyncDisplayKit/ASRunLoopQueue.h b/Source/ASRunLoopQueue.h similarity index 84% rename from AsyncDisplayKit/ASRunLoopQueue.h rename to Source/ASRunLoopQueue.h index cd9a572997..2e50444f3d 100644 --- a/AsyncDisplayKit/ASRunLoopQueue.h +++ b/Source/ASRunLoopQueue.h @@ -11,15 +11,18 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN +AS_SUBCLASSING_RESTRICTED @interface ASRunLoopQueue : NSObject /** * Create a new queue with the given run loop and handler. * * @param runloop The run loop that will drive this queue. + * @param retainsObjects Whether the queue should retain its objects. * @param handlerBlock An optional block to be run for each enqueued object. * * @discussion You may pass @c nil for the handler if you simply want the objects to @@ -28,7 +31,8 @@ NS_ASSUME_NONNULL_BEGIN * worker thread with its own run loop. */ - (instancetype)initWithRunLoop:(CFRunLoopRef)runloop - andHandler:(nullable void(^)(ObjectType dequeuedItem, BOOL isQueueDrained))handlerBlock; + retainObjects:(BOOL)retainsObjects + handler:(nullable void(^)(ObjectType dequeuedItem, BOOL isQueueDrained))handlerBlock; - (void)enqueue:(ObjectType)object; @@ -37,6 +41,7 @@ NS_ASSUME_NONNULL_BEGIN @end +AS_SUBCLASSING_RESTRICTED @interface ASDeallocQueue : NSObject + (instancetype)sharedDeallocationQueue; diff --git a/AsyncDisplayKit/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm similarity index 66% rename from AsyncDisplayKit/ASRunLoopQueue.mm rename to Source/ASRunLoopQueue.mm index 7e50424a59..adc03e0ad0 100644 --- a/AsyncDisplayKit/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -10,19 +10,21 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASRunLoopQueue.h" -#import "ASThread.h" -#import "ASLog.h" +#import +#import +#import +#import #import #import #import #define ASRunLoopQueueLoggingEnabled 0 +#define ASRunLoopQueueVerboseLoggingEnabled 0 static void runLoopSourceCallback(void *info) { // No-op -#if ASRunLoopQueueLoggingEnabled +#if ASRunLoopQueueVerboseLoggingEnabled NSLog(@"<%@> - Called runLoopSourceCallback", info); #endif } @@ -48,6 +50,11 @@ + (instancetype)sharedDeallocationQueue - (void)releaseObjectInBackground:(id)object { + // Disable background deallocation on iOS 8 and below to avoid crashes related to UIAXDelegateClearer (#2767). + if (!AS_AT_LEAST_IOS9) { + return; + } + _queueLock.lock(); _queue.push_back(object); _queueLock.unlock(); @@ -60,19 +67,22 @@ - (void)threadMain // 100ms timer. No resources are wasted in between, as the thread sleeps, and each check is fast. // This time is fast enough for most use cases without excessive churn. CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(NULL, -1, 0.1, 0, 0, ^(CFRunLoopTimerRef timer) { -#if ASRunLoopQueueLoggingEnabled - NSLog(@"ASDeallocQueue Processing: %d objects destroyed", weakSelf->_queue.size()); -#endif weakSelf->_queueLock.lock(); - std::deque currentQueue = weakSelf->_queue; - if (currentQueue.size() == 0) { + if (weakSelf->_queue.size() == 0) { weakSelf->_queueLock.unlock(); return; } - // Sometimes we release 10,000 objects at a time. Don't hold the lock while releasing. - weakSelf->_queue = std::deque(); - weakSelf->_queueLock.unlock(); - currentQueue.clear(); + // The scope below is entered while already locked. @autorelease is crucial here; see PR 2890. + @autoreleasepool { +#if ASRunLoopQueueLoggingEnabled + NSLog(@"ASDeallocQueue Processing: %lu objects destroyed", weakSelf->_queue.size()); +#endif + // Sometimes we release 10,000 objects at a time. Don't hold the lock while releasing. + std::deque currentQueue = weakSelf->_queue; + weakSelf->_queue = std::deque(); + weakSelf->_queueLock.unlock(); + currentQueue.clear(); + } }); CFRunLoopRef runloop = CFRunLoopGetCurrent(); @@ -146,7 +156,7 @@ @interface ASRunLoopQueue () { CFRunLoopRef _runLoop; CFRunLoopSourceRef _runLoopSource; CFRunLoopObserverRef _runLoopObserver; - std::deque _internalQueue; + NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance. ASDN::RecursiveMutex _internalQueueLock; #if ASRunLoopQueueLoggingEnabled @@ -160,11 +170,12 @@ @interface ASRunLoopQueue () { @implementation ASRunLoopQueue -- (instancetype)initWithRunLoop:(CFRunLoopRef)runloop andHandler:(void(^)(id dequeuedItem, BOOL isQueueDrained))handlerBlock +- (instancetype)initWithRunLoop:(CFRunLoopRef)runloop retainObjects:(BOOL)retainsObjects handler:(void (^)(id _Nullable, BOOL))handlerBlock { if (self = [super init]) { _runLoop = runloop; - _internalQueue = std::deque(); + NSPointerFunctionsOptions options = retainsObjects ? NSPointerFunctionsStrongMemory : NSPointerFunctionsWeakMemory; + _internalQueue = [[NSPointerArray alloc] initWithOptions:options]; _queueConsumer = handlerBlock; _batchSize = 1; _ensureExclusiveMembership = YES; @@ -180,16 +191,13 @@ - (instancetype)initWithRunLoop:(CFRunLoopRef)runloop andHandler:(void(^)(id deq // 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)); - if (runLoopSourceContext) { - runLoopSourceContext->perform = runLoopSourceCallback; + CFRunLoopSourceContext sourceContext = {}; + sourceContext.perform = runLoopSourceCallback; #if ASRunLoopQueueLoggingEnabled - runLoopSourceContext->info = (__bridge void *)self; + sourceContext.info = (__bridge void *)self; #endif - _runLoopSource = CFRunLoopSourceCreate(NULL, 0, runLoopSourceContext); - CFRunLoopAddSource(runloop, _runLoopSource, kCFRunLoopCommonModes); - free(runLoopSourceContext); - } + _runLoopSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext); + CFRunLoopAddSource(runloop, _runLoopSource, kCFRunLoopCommonModes); #if ASRunLoopQueueLoggingEnabled _runloopQueueLoggingTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(checkRunLoop) userInfo:nil repeats:YES]; @@ -234,30 +242,57 @@ - (void)processQueue ASDN::MutexLocker l(_internalQueueLock); // Early-exit if the queue is empty. - if (_internalQueue.empty()) { + NSInteger internalQueueCount = _internalQueue.count; + if (internalQueueCount == 0) { return; } ASProfilingSignpostStart(0, self); // Snatch the next batch of items. - auto firstItemToProcess = _internalQueue.cbegin(); - auto lastItemToProcess = MIN(_internalQueue.cend(), firstItemToProcess + self.batchSize); - - if (hasExecutionBlock) { - itemsToProcess = std::vector(firstItemToProcess, lastItemToProcess); + NSInteger maxCountToProcess = MIN(internalQueueCount, self.batchSize); + + /** + * For each item in the next batch, if it's non-nil then NULL it out + * and if we have an execution block then add it in. + * This could be written a bunch of different ways but + * this particular one nicely balances readability, safety, and efficiency. + */ + NSInteger foundItemCount = 0; + for (NSInteger i = 0; i < internalQueueCount && foundItemCount < maxCountToProcess; i++) { + /** + * It is safe to use unsafe_unretained here. If the queue is weak, the + * object will be added to the autorelease pool. If the queue is strong, + * it will retain the object until we transfer it (retain it) in itemsToProcess. + */ + __unsafe_unretained id ptr = (__bridge id)[_internalQueue pointerAtIndex:i]; + if (ptr != nil) { + foundItemCount++; + if (hasExecutionBlock) { + itemsToProcess.push_back(ptr); + } + [_internalQueue replacePointerAtIndex:i withPointer:NULL]; + } } - _internalQueue.erase(firstItemToProcess, lastItemToProcess); - if (_internalQueue.empty()) { + [_internalQueue compact]; + if (_internalQueue.count == 0) { isQueueDrained = YES; } } // itemsToProcess will be empty if _queueConsumer == nil so no need to check again. - auto itemsEnd = itemsToProcess.cend(); - for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) { - _queueConsumer(*iterator, isQueueDrained && iterator == itemsEnd - 1); + if (itemsToProcess.empty() == false) { +#if ASRunLoopQueueLoggingEnabled + NSLog(@"<%@> - Starting processing of: %ld", self, itemsToProcess.size()); +#endif + auto itemsEnd = itemsToProcess.cend(); + for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) { + _queueConsumer(*iterator, isQueueDrained && iterator == itemsEnd - 1); +#if ASRunLoopQueueLoggingEnabled + NSLog(@"<%@> - Finished processing 1 item", self); +#endif + } } // If the queue is not fully drained yet force another run loop to process next batch of items @@ -281,7 +316,7 @@ - (void)enqueue:(id)object BOOL foundObject = NO; if (_ensureExclusiveMembership) { - for (id currentObject : _internalQueue) { + for (id currentObject in _internalQueue) { if (currentObject == object) { foundObject = YES; break; @@ -290,7 +325,7 @@ - (void)enqueue:(id)object } if (!foundObject) { - _internalQueue.push_back(object); + [_internalQueue addPointer:(__bridge void *)object]; CFRunLoopSourceSignal(_runLoopSource); CFRunLoopWakeUp(_runLoop); diff --git a/Source/ASScrollNode.h b/Source/ASScrollNode.h new file mode 100644 index 0000000000..5adbedf62d --- /dev/null +++ b/Source/ASScrollNode.h @@ -0,0 +1,51 @@ +// +// ASScrollNode.h +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class UIScrollView; + +/** + * Simple node that wraps UIScrollView. + */ +@interface ASScrollNode : ASDisplayNode + +/** + * @abstract The node's UIScrollView. + */ +@property (nonatomic, readonly, strong) UIScrollView *view; + +/** + * @abstract When enabled, the size calculated by the node's layout spec is used as + * the .contentSize of the scroll view, instead of the bounds size. The bounds is instead + * allowed to match the parent's size (whenever it is finite - otherwise, the bounds size + * also grows to the full contentSize). It also works with .layoutSpecBlock(). + * NOTE: Most users of ASScrollView will want to use this, and may be enabled by default later. + */ +@property (nonatomic, assign) BOOL automaticallyManagesContentSize; + +/** + * @abstract This property controls how the constrainedSize is interpreted when sizing the content. + * if you are using automaticallyManagesContentSize, it can be crucial to ensure that the sizing is + * done how you expect. + * Vertical: The constrainedSize is interpreted as having unbounded .height (CGFLOAT_MAX), allowing + * stacks and other content in the layout spec to expand and result in scrollable content. + * Horizontal: The constrainedSize is interpreted as having unbounded .width (CGFLOAT_MAX), ... + * Vertical & Horizontal: the constrainedSize is interpreted as unbounded in both directions. + * @default ASScrollDirectionVerticalDirections + */ +@property (nonatomic, assign) ASScrollDirection scrollableDirections; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ASScrollNode.mm b/Source/ASScrollNode.mm new file mode 100644 index 0000000000..7d257dd99b --- /dev/null +++ b/Source/ASScrollNode.mm @@ -0,0 +1,160 @@ +// +// ASScrollNode.m +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import +#import +#import +#import +#import + +@interface ASScrollView : UIScrollView +@end + +@implementation ASScrollView + +// This special +layerClass allows ASScrollNode to get -layout calls from -layoutSublayers. ++ (Class)layerClass +{ + return [_ASDisplayLayer class]; +} + +- (ASScrollNode *)scrollNode +{ + return (ASScrollNode *)ASViewToDisplayNode(self); +} + +#pragma mark - _ASDisplayView behavior substitutions +// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. +// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. +- (void)willMoveToWindow:(UIWindow *)newWindow +{ + ASDisplayNode *node = self.scrollNode; // Create strong reference to weak ivar. + BOOL visible = (newWindow != nil); + if (visible && !node.inHierarchy) { + [node __enterHierarchy]; + } +} + +- (void)didMoveToWindow +{ + ASDisplayNode *node = self.scrollNode; // Create strong reference to weak ivar. + BOOL visible = (self.window != nil); + if (!visible && node.inHierarchy) { + [node __exitHierarchy]; + } +} + +@end + +@implementation ASScrollNode +{ + ASScrollDirection _scrollableDirections; + BOOL _automaticallyManagesContentSize; + CGSize _contentCalculatedSizeFromLayout; +} +@dynamic view; + +- (instancetype)init +{ + if (self = [super init]) { + [self setViewBlock:^UIView *{ return [[ASScrollView alloc] init]; }]; + } + return self; +} + +- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize + restrictedToSize:(ASLayoutElementSize)size + relativeToParentSize:(CGSize)parentSize +{ + ASDN::MutexLocker l(__instanceLock__); // Lock for using our instance variables. + + ASSizeRange contentConstrainedSize = constrainedSize; + if (ASScrollDirectionContainsVerticalDirection(_scrollableDirections)) { + contentConstrainedSize.max.height = CGFLOAT_MAX; + } + if (ASScrollDirectionContainsHorizontalDirection(_scrollableDirections)) { + contentConstrainedSize.max.width = CGFLOAT_MAX; + } + + ASLayout *layout = [super calculateLayoutThatFits:contentConstrainedSize + restrictedToSize:size + relativeToParentSize:parentSize]; + + if (_automaticallyManagesContentSize) { + // To understand this code, imagine we're containing a horizontal stack set within a vertical table node. + // Our parentSize is fixed ~375pt width, but 0 - INF height. Our stack measures 1000pt width, 50pt height. + // In this case, we want our scrollNode.bounds to be 375pt wide, and 50pt high. ContentSize 1000pt, 50pt. + // We can achieve this behavior by: 1. Always set contentSize to layout.size. 2. Set bounds to parentSize, + // unless one dimension is not defined, in which case adopt the contentSize for that dimension. + _contentCalculatedSizeFromLayout = layout.size; + CGSize selfSize = parentSize; + if (ASPointsValidForLayout(selfSize.width) == NO) { + selfSize.width = _contentCalculatedSizeFromLayout.width; + } + if (ASPointsValidForLayout(selfSize.height) == NO) { + selfSize.height = _contentCalculatedSizeFromLayout.height; + } + // Don't provide a position, as that should be set by the parent. + layout = [ASLayout layoutWithLayoutElement:self + size:selfSize + sublayouts:layout.sublayouts]; + } + return layout; +} + +- (void)layout +{ + [super layout]; + + ASDN::MutexLocker l(__instanceLock__); // Lock for using our two instance variables. + + if (_automaticallyManagesContentSize) { + CGSize contentSize = _contentCalculatedSizeFromLayout; + if (ASIsCGSizeValidForLayout(contentSize) == NO) { + NSLog(@"%@ calculated a size in its layout spec that can't be applied to .contentSize: %@. Applying parentSize (scrollNode's bounds) instead: %@.", self, NSStringFromCGSize(contentSize), NSStringFromCGSize(self.calculatedSize)); + contentSize = self.calculatedSize; + } + self.view.contentSize = contentSize; + } +} + +- (BOOL)automaticallyManagesContentSize +{ + ASDN::MutexLocker l(__instanceLock__); + return _automaticallyManagesContentSize; +} + +- (void)setAutomaticallyManagesContentSize:(BOOL)automaticallyManagesContentSize +{ + ASDN::MutexLocker l(__instanceLock__); + _automaticallyManagesContentSize = automaticallyManagesContentSize; + if (_automaticallyManagesContentSize == YES + && ASScrollDirectionContainsVerticalDirection(_scrollableDirections) == NO + && ASScrollDirectionContainsHorizontalDirection(_scrollableDirections) == NO) { + // Set the @default value, for more user-friendly behavior of the most + // common use cases of .automaticallyManagesContentSize. + _scrollableDirections = ASScrollDirectionVerticalDirections; + } +} + +- (ASScrollDirection)scrollableDirections +{ + ASDN::MutexLocker l(__instanceLock__); + return _scrollableDirections; +} + +- (void)setScrollableDirections:(ASScrollDirection)scrollableDirections +{ + ASDN::MutexLocker l(__instanceLock__); + _scrollableDirections = scrollableDirections; +} + +@end diff --git a/Source/ASSectionController.h b/Source/ASSectionController.h new file mode 100644 index 0000000000..f57413f07d --- /dev/null +++ b/Source/ASSectionController.h @@ -0,0 +1,73 @@ +// +// ASSectionController.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/19/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class ASBatchContext; + +/** + * A protocol that your section controllers should conform to, + * in addition to IGListSectionType, in order to be used with AsyncDisplayKit. + * + * @note Your supplementary view source should conform to @c ASSupplementaryNodeSource. + */ +@protocol ASSectionController + +/** + * A method to provide the node block for the item at the given index. + * The node block you return will be run asynchronously off the main thread, + * so it's important to retrieve any objects from your section _outside_ the block + * because by the time the block is run, the array may have changed. + * + * @param index The index of the item. + * @return A block to be run concurrently to build the node for this item. + * @see collectionNode:nodeBlockForItemAtIndexPath: + */ +- (ASCellNodeBlock)nodeBlockForItemAtIndex:(NSInteger)index; + +@optional + +/** + * Asks the section controller whether it should batch fetch because the user is + * near the end of the current data set. + * + * @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of + * objects that can be fetched or no network connection. + * + * If not implemented, the assumed return value is @c YES. + */ +- (BOOL)shouldBatchFetch; + +/** + * Asks the section controller to begin fetching more content (tail loading) because + * the user is near the end of the current data set. + * + * @param context A context object that must be notified when the batch fetch is completed. + * + * @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future + * notifications to do batch fetches. This method is called on a background queue. + */ +- (void)beginBatchFetchWithContext:(ASBatchContext *)context; + +/** + * A method to provide the size range used for measuring the item + * at the given index. + * + * @param index The index of the item. + * @return A size range used for asynchronously measuring the node at this index. + * @see collectionNode:constrainedSizeForItemAtIndexPath: + */ +- (ASSizeRange)sizeRangeForItemAtIndex:(NSInteger)index; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ASSupplementaryNodeSource.h b/Source/ASSupplementaryNodeSource.h new file mode 100644 index 0000000000..52ad429377 --- /dev/null +++ b/Source/ASSupplementaryNodeSource.h @@ -0,0 +1,42 @@ +// +// ASSupplementaryNodeSource.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/19/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol ASSupplementaryNodeSource + +/** + * A method to provide the node-block for the supplementary element. + * + * @param elementKind The kind of supplementary element. + * @param index The index of the item. + * @return A node block for the supplementary element. + * @see collectionNode:nodeForSupplementaryElementOfKind:atIndexPath: + */ +- (ASCellNodeBlock)nodeBlockForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index; + +@optional + +/** + * A method to provide the size range used for measuring the supplementary + * element of the given kind at the given index. + * + * @param elementKind The kind of supplementary element. + * @param index The index of the item. + * @return A size range used for asynchronously measuring the node. + * @see collectionNode:constrainedSizeForSupplementaryElementOfKind:atIndexPath: + */ +- (ASSizeRange)sizeRangeForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASTabBarController.h b/Source/ASTabBarController.h similarity index 94% rename from AsyncDisplayKit/ASTabBarController.h rename to Source/ASTabBarController.h index 04c35d73b8..71549ba45d 100644 --- a/AsyncDisplayKit/ASTabBarController.h +++ b/Source/ASTabBarController.h @@ -12,7 +12,7 @@ #import -#import "ASVisibilityProtocols.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/ASTabBarController.m b/Source/ASTabBarController.m similarity index 97% rename from AsyncDisplayKit/ASTabBarController.m rename to Source/ASTabBarController.m index 8a82ff11b1..00f9cd0407 100644 --- a/AsyncDisplayKit/ASTabBarController.m +++ b/Source/ASTabBarController.m @@ -10,7 +10,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTabBarController.h" +#import @implementation ASTabBarController { diff --git a/AsyncDisplayKit/ASTableNode.h b/Source/ASTableNode.h similarity index 97% rename from AsyncDisplayKit/ASTableNode.h rename to Source/ASTableNode.h index 019a910953..61d5909112 100644 --- a/AsyncDisplayKit/ASTableNode.h +++ b/Source/ASTableNode.h @@ -10,15 +10,17 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import +#import #import #import +#import + NS_ASSUME_NONNULL_BEGIN @protocol ASTableDataSource; @protocol ASTableDelegate; -@class ASTableView; +@class ASTableView, ASBatchContext; /** * ASTableNode is a node based class that wraps an ASTableView. It can be used @@ -27,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN @interface ASTableNode : ASDisplayNode - (instancetype)init; // UITableViewStylePlain -- (instancetype)initWithStyle:(UITableViewStyle)style; +- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER; @property (strong, nonatomic, readonly) ASTableView *view; @@ -35,6 +37,11 @@ NS_ASSUME_NONNULL_BEGIN @property (weak, nonatomic) id delegate; @property (weak, nonatomic) id dataSource; +/* + * A Boolean value that determines whether the table will be flipped. + * If the value of this property is YES, the first cell node will be at the bottom of the table (as opposed to the top by default). This is useful for chat/messaging apps. The default value is NO. + */ +@property (nonatomic, assign) BOOL inverted; /* * A Boolean value that determines whether users can select a row. * If the value of this property is YES (the default), users can select rows. If you set it to NO, they cannot select rows. Setting this property affects cell selection only when the table view is not in editing mode. If you want to restrict selection of cells in editing mode, use `allowsSelectionDuringEditing`. @@ -148,7 +155,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. @@ -159,7 +166,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. @@ -456,7 +463,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Similar to -tableView:cellForRowAtIndexPath:. * - * @param tableNode The sender. + * @param tableView The sender. * * @param indexPath The index path of the requested node. * @@ -545,7 +552,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Receive a message that the tableView is near the end of its data set and more data should be fetched if necessary. * - * @param tableView The sender. + * @param tableNode The sender. * @param context A context object that must be notified when the batch fetch is completed. * * @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future @@ -559,7 +566,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Tell the tableView if batch fetching should begin. * - * @param tableView The sender. + * @param tableNode The sender. * * @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of * objects that can be fetched or no network connection. diff --git a/AsyncDisplayKit/ASTableNode.mm b/Source/ASTableNode.mm similarity index 77% rename from AsyncDisplayKit/ASTableNode.mm rename to Source/ASTableNode.mm index 024f207ae8..92fb5b006d 100644 --- a/AsyncDisplayKit/ASTableNode.mm +++ b/Source/ASTableNode.mm @@ -10,15 +10,19 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTableNode.h" -#import "ASTableViewInternal.h" -#import "ASEnvironmentInternal.h" -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASInternalHelpers.h" -#import "ASCellNode+Internal.h" -#import "AsyncDisplayKit+Debug.h" -#import "ASTableView+Undeprecated.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import #pragma mark - _ASTablePendingState @@ -30,6 +34,7 @@ @interface _ASTablePendingState : NSObject @property (nonatomic, assign) BOOL allowsSelectionDuringEditing; @property (nonatomic, assign) BOOL allowsMultipleSelection; @property (nonatomic, assign) BOOL allowsMultipleSelectionDuringEditing; +@property (nonatomic, assign) BOOL inverted; @end @implementation _ASTablePendingState @@ -37,11 +42,12 @@ - (instancetype)init { self = [super init]; if (self) { - _rangeMode = ASLayoutRangeModeCount; + _rangeMode = ASLayoutRangeModeUnspecified; _allowsSelection = YES; _allowsSelectionDuringEditing = NO; _allowsMultipleSelection = NO; _allowsMultipleSelectionDuringEditing = NO; + _inverted = NO; } return self; } @@ -58,47 +64,26 @@ @interface ASTableNode () @property (nonatomic, strong) _ASTablePendingState *pendingState; @end -@interface ASTableView () -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass; -@end - @implementation ASTableNode #pragma mark Lifecycle -- (instancetype)_initWithTableView:(ASTableView *)tableView -{ - // Avoid a retain cycle. In this case, the ASTableView is creating us, and strongly retains us. - ASTableView * __weak weakTableView = tableView; - if (self = [super initWithViewBlock:^UIView *{ return weakTableView; }]) { - __unused __weak ASTableView *view = [self view]; - return self; - } - return nil; -} - -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass -{ - __weak __typeof__(self) weakSelf = self; - ASDisplayNodeViewBlock tableViewBlock = ^UIView *{ - __typeof__(self) strongSelf = weakSelf; - return [[ASTableView alloc] _initWithFrame:frame style:style dataControllerClass:dataControllerClass eventLog:ASDisplayNodeGetEventLog(strongSelf)]; - }; - - if (self = [super initWithViewBlock:tableViewBlock]) { - return self; - } - return nil; -} - - (instancetype)initWithStyle:(UITableViewStyle)style { - return [self _initWithFrame:CGRectZero style:style dataControllerClass:nil]; + if (self = [super init]) { + __weak __typeof__(self) weakSelf = self; + [self setViewBlock:^{ + // Variable will be unused if event logging is off. + __unused __typeof__(self) strongSelf = weakSelf; + return [[ASTableView alloc] _initWithFrame:CGRectZero style:style dataControllerClass:nil eventLog:ASDisplayNodeGetEventLog(strongSelf)]; + }]; + } + return self; } - (instancetype)init { - return [self _initWithFrame:CGRectZero style:UITableViewStylePlain dataControllerClass:nil]; + return [self initWithStyle:UITableViewStylePlain]; } #pragma mark ASDisplayNode @@ -115,11 +100,12 @@ - (void)didLoad self.pendingState = nil; view.asyncDelegate = pendingState.delegate; view.asyncDataSource = pendingState.dataSource; + view.inverted = pendingState.inverted; view.allowsSelection = pendingState.allowsSelection; view.allowsSelectionDuringEditing = pendingState.allowsSelectionDuringEditing; view.allowsMultipleSelection = pendingState.allowsMultipleSelection; view.allowsMultipleSelectionDuringEditing = pendingState.allowsMultipleSelectionDuringEditing; - if (pendingState.rangeMode != ASLayoutRangeModeCount) { + if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; } } @@ -136,18 +122,20 @@ - (void)clearContents [self.rangeController clearContents]; } -- (void)clearFetchedData -{ - [super clearFetchedData]; - [self.rangeController clearFetchedData]; -} - - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState { [super interfaceStateDidChange:newState fromState:oldState]; [ASRangeController layoutDebugOverlayIfNeeded]; } +- (void)didEnterPreloadState +{ + // Intentionally allocate the view here so that super will trigger a layout pass on it which in turn will trigger the intial data load. + // We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view. + [self view]; + [super didEnterPreloadState]; +} + #if ASRangeControllerLoggingEnabled - (void)didEnterVisibleState { @@ -162,9 +150,15 @@ - (void)didExitVisibleState } #endif +- (void)didExitPreloadState +{ + [super didExitPreloadState]; + [self.rangeController clearPreloadedData]; +} + #pragma mark Setter / Getter -// TODO: Implement this without the view. +// TODO: Implement this without the view. Then revisit ASLayoutElementCollectionTableSetTraitCollection - (ASDataController *)dataController { return self.view.dataController; @@ -185,6 +179,26 @@ - (_ASTablePendingState *)pendingState return _pendingState; } +- (void)setInverted:(BOOL)inverted +{ + self.transform = inverted ? CATransform3DMakeScale(1, -1, 1) : CATransform3DIdentity; + if ([self pendingState]) { + _pendingState.inverted = inverted; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); + self.view.inverted = inverted; + } +} + +- (BOOL)inverted +{ + if ([self pendingState]) { + return _pendingState.inverted; + } else { + return self.view.inverted; + } +} + - (void)setDelegate:(id )delegate { if ([self pendingState]) { @@ -196,7 +210,7 @@ - (void)setDelegate:(id )delegate // and asserting here isn't an option – it is a common pattern for users to clear // the delegate/dataSource in dealloc, which may be running on a background thread. // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASTableView *view = (ASTableView *)_view; + ASTableView *view = self.view; ASPerformBlockOnMainThread(^{ view.asyncDelegate = delegate; }); @@ -223,7 +237,7 @@ - (void)setDataSource:(id )dataSource // and asserting here isn't an option – it is a common pattern for users to clear // the delegate/dataSource in dealloc, which may be running on a background thread. // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASTableView *view = (ASTableView *)_view; + ASTableView *view = self.view; ASPerformBlockOnMainThread(^{ view.asyncDataSource = dataSource; }); @@ -330,7 +344,7 @@ - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode #pragma mark ASEnvironment -ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock) +ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock) #pragma mark - Range Tuning @@ -413,14 +427,14 @@ - (NSInteger)numberOfRowsInSection:(NSInteger)section { ASDisplayNodeAssertMainThread(); [self reloadDataInitiallyIfNeeded]; - return [self.dataController numberOfRowsInSection:section]; + return [self.dataController.pendingMap numberOfItemsInSection:section]; } - (NSInteger)numberOfSections { ASDisplayNodeAssertMainThread(); [self reloadDataInitiallyIfNeeded]; - return [self.dataController numberOfSections]; + return [self.dataController.pendingMap numberOfSections]; } - (NSArray<__kindof ASCellNode *> *)visibleNodes @@ -431,13 +445,13 @@ - (NSInteger)numberOfSections - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode { - return [self.dataController indexPathForNode:cellNode]; + return [self.dataController.pendingMap indexPathForElement:cellNode.collectionElement]; } - (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath { [self reloadDataInitiallyIfNeeded]; - return [self.dataController nodeAtIndexPath:indexPath]; + return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node; } - (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath @@ -517,7 +531,14 @@ - (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point - (void)reloadDataWithCompletion:(void (^)())completion { - [self.view reloadDataWithCompletion:completion]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view reloadDataWithCompletion:completion]; + } else { + if (completion) { + completion(); + } + } } - (void)reloadData @@ -532,11 +553,19 @@ - (void)relayoutItems - (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion { - [self.view beginUpdates]; - if (updates) { - updates(); + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + ASTableView *tableView = self.view; + [tableView beginUpdates]; + if (updates) { + updates(); + } + [tableView endUpdatesAnimated:animated completion:completion]; + } else { + if (updates) { + updates(); + } } - [self.view endUpdatesAnimated:animated completion:completion]; } - (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion @@ -546,47 +575,74 @@ - (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))compl - (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { - [self.view insertSections:sections withRowAnimation:animation]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view insertSections:sections withRowAnimation:animation]; + } } - (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { - [self.view deleteSections:sections withRowAnimation:animation]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view deleteSections:sections withRowAnimation:animation]; + } } - (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { - [self.view reloadSections:sections withRowAnimation:animation]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view reloadSections:sections withRowAnimation:animation]; + } } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { - [self.view moveSection:section toSection:newSection]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view moveSection:section toSection:newSection]; + } } - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { - [self.view insertRowsAtIndexPaths:indexPaths withRowAnimation:animation]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view insertRowsAtIndexPaths:indexPaths withRowAnimation:animation]; + } } - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { - [self.view deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation]; + } } - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { - [self.view reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation]; + } } - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { - [self.view moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; + } } - (void)waitUntilAllUpdatesAreCommitted { - [self.view waitUntilAllUpdatesAreCommitted]; + ASDisplayNodeAssertMainThread(); + if (self.nodeLoaded) { + [self.view waitUntilAllUpdatesAreCommitted]; + } } #pragma mark - Debugging (Private) diff --git a/AsyncDisplayKit/ASTableView.h b/Source/ASTableView.h similarity index 96% rename from AsyncDisplayKit/ASTableView.h rename to Source/ASTableView.h index b932edc873..01d1e6f578 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/Source/ASTableView.h @@ -9,10 +9,10 @@ // #import -#import -#import + #import -#import +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -44,9 +44,11 @@ NS_ASSUME_NONNULL_BEGIN - (nullable ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; /** - * YES to automatically adjust the contentOffset when cells are inserted or deleted "before" - * visible cells, maintaining the users' visible scroll position. Currently this feature tracks insertions, moves and deletions of - * cells, but section edits are ignored. + * YES to automatically adjust the contentOffset when cells are inserted or deleted above + * visible cells, maintaining the users' visible scroll position. + * + * @note This is only applied to non-animated updates. For animated updates, there is no way to + * synchronize or "cancel out" the appearance of a scroll due to UITableView API limitations. * * default is NO. */ @@ -59,6 +61,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) CGFloat leadingScreensForBatching; +/* + * A Boolean value that determines whether the nodes that the data source renders will be flipped. + */ +@property (nonatomic, assign) BOOL inverted; + @end @interface ASTableView (Deprecated) diff --git a/AsyncDisplayKit/ASTableView.mm b/Source/ASTableView.mm similarity index 72% rename from AsyncDisplayKit/ASTableView.mm rename to Source/ASTableView.mm index 99220dbad5..e2b92ab140 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/Source/ASTableView.mm @@ -8,24 +8,28 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTableViewInternal.h" - -#import "ASAssert.h" -#import "ASAvailability.h" -#import "ASBatchFetching.h" -#import "ASCellNode+Internal.h" -#import "ASChangeSetDataController.h" -#import "ASDelegateProxy.h" -#import "ASDisplayNodeExtras.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASInternalHelpers.h" -#import "ASLayout.h" -#import "_ASDisplayLayer.h" -#import "ASTableNode.h" -#import "ASEqualityHelpers.h" -#import "ASTableView+Undeprecated.h" - -static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero}; +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; //#define LOG(...) NSLog(__VA_ARGS__) @@ -73,6 +77,21 @@ - (void)didTransitionToState:(UITableViewCellStateMask)state - (void)setNode:(ASCellNode *)node { _node = node; + + if (node) { + self.backgroundColor = node.backgroundColor; + self.selectionStyle = node.selectionStyle; + self.selectedBackgroundView = node.selectedBackgroundView; + self.separatorInset = node.separatorInset; + self.selectionStyle = node.selectionStyle; + self.accessoryType = node.accessoryType; + + // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) + // This is actually a workaround for a bug we are seeing in some rare cases (selected background view + // overlaps other cells if size of ASCellNode has changed.) + self.clipsToBounds = node.clipsToBounds; + } + [node __setSelectedFromUIKit:self.selected]; [node __setHighlightedFromUIKit:self.highlighted]; } @@ -101,25 +120,25 @@ - (void)prepareForReuse #pragma mark - #pragma mark ASTableView -@interface ASTableNode () -- (instancetype)_initWithTableView:(ASTableView *)tableView; -@end - @interface ASTableView () { ASTableViewProxy *_proxyDataSource; ASTableViewProxy *_proxyDelegate; - ASFlowLayoutController *_layoutController; + ASTableLayoutController *_layoutController; ASRangeController *_rangeController; ASBatchContext *_batchContext; - NSIndexPath *_pendingVisibleIndexPath; + // When we update our data controller in response to an interactive move, + // we don't want to tell the table view about the change (it knows!) + BOOL _updatingInResponseToInteractiveMove; - NSIndexPath *_contentOffsetAdjustmentTopVisibleRow; - CGFloat _contentOffsetAdjustment; + // The top cell node that was visible before the update. + __weak ASCellNode *_contentOffsetAdjustmentTopVisibleNode; + // The y-offset of the top visible row's origin before the update. + CGFloat _contentOffsetAdjustmentTopVisibleNodeOffset; CGPoint _deceleratingVelocity; @@ -133,21 +152,36 @@ @interface ASTableView () )_proxyDelegate; _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; super.dataSource = (id)_proxyDataSource; - + [self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier]; } @@ -270,6 +299,7 @@ - (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataC return nil; } _cellsForVisibilityUpdates = [NSMutableSet set]; + _cellsForLayoutUpdates = [NSMutableSet set]; if (!dataControllerClass) { dataControllerClass = [[self class] dataControllerClass]; } @@ -292,6 +322,8 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder - (void)dealloc { ASDisplayNodeAssertMainThread(); + ASDisplayNodeCAssert(_batchUpdateCount == 0, @"ASTableView deallocated in the middle of a batch update."); + // Sometimes the UIKit classes can call back to their delegate even during deallocation. _isDeallocating = YES; [self setAsyncDelegate:nil]; @@ -361,6 +393,7 @@ - (void)setAsyncDataSource:(id)asyncDataSource ASDisplayNodeAssert(_asyncDataSourceFlags.tableNodeNumberOfRowsInSection || _asyncDataSourceFlags.tableViewNumberOfRowsInSection, @"Data source must implement tableNode:numberOfRowsInSection:"); } + _dataController.validationErrorSource = asyncDataSource; super.dataSource = (id)_proxyDataSource; } @@ -399,6 +432,7 @@ - (void)setAsyncDelegate:(id)asyncDelegate _asyncDelegateFlags.tableViewDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didEndDisplayingRowWithNode:)]; _asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; + _asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]; _asyncDelegateFlags.tableViewWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]; _asyncDelegateFlags.tableNodeWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableNode:willBeginBatchFetchWithContext:)]; _asyncDelegateFlags.shouldBatchFetchForTableView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]; @@ -444,10 +478,23 @@ - (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy - (void)reloadDataWithCompletion:(void (^)())completion { - ASPerformBlockOnMainThread(^{ + ASDisplayNodeAssertMainThread(); + + if (! _dataController.initialReloadDataHasBeenCalled) { + // If this is the first reload, forward to super immediately to prevent it from triggering more "initial" loads while our data controller is working. [super reloadData]; - }); - [_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion]; + } + + void (^batchUpdatesCompletion)(BOOL); + if (completion) { + batchUpdatesCompletion = ^(BOOL) { + completion(); + }; + } + + [self beginUpdates]; + [_changeSet reloadData]; + [self endUpdatesWithCompletion:batchUpdatesCompletion]; } - (void)reloadData @@ -458,8 +505,15 @@ - (void)reloadData - (void)reloadDataImmediately { ASDisplayNodeAssertMainThread(); - [_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone]; - [super reloadData]; + [self reloadData]; + [_dataController waitUntilAllUpdatesAreCommitted]; +} + +- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated +{ + if ([self validateIndexPath:indexPath]) { + [super scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; + } } - (void)relayoutItems @@ -492,37 +546,44 @@ - (ASTableNode *)tableNode return (ASTableNode *)ASViewToDisplayNode(self); } -- (NSArray *> *)completedNodes +- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController { - return [_dataController completedNodes]; + return _dataController.visibleMap; } - (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath { - return [_dataController nodeAtCompletedIndexPath:indexPath]; + return [_dataController.visibleMap elementForItemAtIndexPath:indexPath].node; } - (NSIndexPath *)convertIndexPathFromTableNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait { // If this is a section index path, we don't currently have a method // to do a mapping. - if (indexPath.row == NSNotFound) { + if (indexPath == nil || indexPath.row == NSNotFound) { return indexPath; } else { - ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; - return [self indexPathForNode:node waitingIfNeeded:wait]; + NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap]; + if (viewIndexPath == nil && wait) { + [self waitUntilAllUpdatesAreCommitted]; + return [self convertIndexPathFromTableNode:indexPath waitingIfNeeded:NO]; + } + return viewIndexPath; } } - (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath { + if ([self validateIndexPath:indexPath] == nil) { + return nil; + } + // If this is a section index path, we don't currently have a method // to do a mapping. if (indexPath.row == NSNotFound) { return indexPath; } else { - ASCellNode *node = [self nodeForRowAtIndexPath:indexPath]; - return [_dataController indexPathForNode:node]; + return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap]; } } @@ -548,72 +609,130 @@ - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode return [self indexPathForNode:cellNode waitingIfNeeded:NO]; } +/** + * Asserts that the index path is a valid view-index-path, and returns it if so, nil otherwise. + */ +- (nullable NSIndexPath *)validateIndexPath:(nullable NSIndexPath *)indexPath +{ + if (indexPath == nil) { + return nil; + } + + NSInteger section = indexPath.section; + if (section >= self.numberOfSections) { + ASDisplayNodeFailAssert(@"Table view index path has invalid section %lu, section count = %lu", (unsigned long)section, (unsigned long)self.numberOfSections); + return nil; + } + + NSInteger item = indexPath.item; + // item == NSNotFound means e.g. "scroll to this section" and is acceptable + if (item != NSNotFound && item >= [self numberOfRowsInSection:section]) { + ASDisplayNodeFailAssert(@"Table view index path has invalid item %lu in section %lu, item count = %lu", (unsigned long)indexPath.item, (unsigned long)section, (unsigned long)[self numberOfRowsInSection:section]); + return nil; + } + + return indexPath; +} + - (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode waitingIfNeeded:(BOOL)wait { - NSIndexPath *indexPath = [_dataController completedIndexPathForNode:cellNode]; + if (cellNode == nil) { + return nil; + } + + NSIndexPath *indexPath = [_dataController.visibleMap indexPathForElement:cellNode.collectionElement]; + indexPath = [self validateIndexPath:indexPath]; if (indexPath == nil && wait) { - [_dataController waitUntilAllUpdatesAreCommitted]; - indexPath = [_dataController completedIndexPathForNode:cellNode]; + [self waitUntilAllUpdatesAreCommitted]; + return [self indexPathForNode:cellNode waitingIfNeeded:NO]; } return indexPath; } - (NSArray *)visibleNodes { - NSArray *indexPaths = [self visibleNodeIndexPathsForRangeController:_rangeController]; - - NSMutableArray *visibleNodes = [NSMutableArray array]; - for (NSIndexPath *indexPath in indexPaths) { - ASCellNode *node = [self nodeForRowAtIndexPath:indexPath]; - if (node) { - // It is possible for UITableView to return indexPaths before the node is completed. - [visibleNodes addObject:node]; - } - } - - return visibleNodes; + NSArray *elements = [self visibleElementsForRangeController:_rangeController]; + return ASArrayByFlatMapping(elements, ASCollectionElement *e, e.node); } - (void)beginUpdates { ASDisplayNodeAssertMainThread(); - [_dataController beginUpdates]; + // _changeSet must be available during batch update + ASDisplayNodeAssertTrue((_batchUpdateCount > 0) == (_changeSet != nil)); + + if (_batchUpdateCount == 0) { + _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[_dataController itemCountsFromDataSource]]; + } + _batchUpdateCount++; } - (void)endUpdates { - [self endUpdatesAnimated:YES completion:nil]; + [self endUpdatesWithCompletion:nil]; } -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL completed))completion; +- (void)endUpdatesWithCompletion:(void (^)(BOOL completed))completion +{ + // We capture the current state of whether animations are enabled if they don't provide us with one. + [self endUpdatesAnimated:[UIView areAnimationsEnabled] completion:completion]; +} + +- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL completed))completion { ASDisplayNodeAssertMainThread(); - [_dataController endUpdatesAnimated:animated completion:completion]; + ASDisplayNodeAssertNotNil(_changeSet, @"_changeSet must be available when batch update ends"); + + _batchUpdateCount--; + // Prevent calling endUpdatesAnimated:completion: in an unbalanced way + NSAssert(_batchUpdateCount >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); + + [_changeSet addCompletionHandler:completion]; + + if (_batchUpdateCount == 0) { + _ASHierarchyChangeSet *changeSet = _changeSet; + // Nil out _changeSet before forwarding to _dataController to allow the change set to cause subsequent batch updates on the same run loop + _changeSet = nil; + changeSet.animated = animated; + [_dataController updateWithChangeSet:changeSet]; + } } - (void)waitUntilAllUpdatesAreCommitted { ASDisplayNodeAssertMainThread(); + if (_batchUpdateCount > 0) { + // This assertion will be enabled soon. + // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); + return; + } + [_dataController waitUntilAllUpdatesAreCommitted]; } - (void)layoutSubviews { + // Remeasure all rows if our row width has changed. + _remeasuringCellNodes = YES; CGFloat constrainedWidth = self.bounds.size.width - [self sectionIndexWidth]; - if (_nodesConstrainedWidth != constrainedWidth) { + if (constrainedWidth > 0 && _nodesConstrainedWidth != constrainedWidth) { _nodesConstrainedWidth = constrainedWidth; - // First width change occurs during initial configuration. An expensive relayout pass is unnecessary at that time - // and should be avoided, assuming that the initial data loading automatically runs shortly afterward. - if (_ignoreNodesConstrainedWidthChange) { - _ignoreNodesConstrainedWidthChange = NO; - } else { - [self beginUpdates]; - [_dataController relayoutAllNodes]; - [self endUpdates]; + [self beginUpdates]; + [_dataController relayoutAllNodes]; + [self endUpdatesAnimated:(ASDisplayNodeLayerHasAnimations(self.layer) == NO) completion:nil]; + } else { + if (_cellsForLayoutUpdates.count > 0) { + NSMutableArray *nodesSizesChanged = [NSMutableArray array]; + [_dataController relayoutNodes:_cellsForLayoutUpdates nodesSizeChanged:nodesSizesChanged]; + if (nodesSizesChanged.count > 0) { + [self requeryNodeHeights]; + } } } - + [_cellsForLayoutUpdates removeAllObjects]; + _remeasuringCellNodes = NO; + // To ensure _nodesConstrainedWidth is up-to-date for every usage, this call to super must be done last [super layoutSubviews]; [_rangeController updateIfNeeded]; @@ -626,54 +745,70 @@ - (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAn { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController insertSections:sections withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet insertSections:sections animationOptions:animation]; + [self endUpdates]; } - (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController deleteSections:sections withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet deleteSections:sections animationOptions:animation]; + [self endUpdates]; } - (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController reloadSections:sections withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet reloadSections:sections animationOptions:animation]; + [self endUpdates]; } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; + [self beginUpdates]; + [_changeSet moveSection:section toSection:newSection animationOptions:UITableViewRowAnimationNone]; + [self endUpdates]; } - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController insertRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet insertItems:indexPaths animationOptions:animation]; + [self endUpdates]; } - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet deleteItems:indexPaths animationOptions:animation]; + [self endUpdates]; } - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet reloadItems:indexPaths animationOptions:animation]; + [self endUpdates]; } - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; + [self beginUpdates]; + [_changeSet moveItemAtIndexPath:indexPath toIndexPath:newIndexPath animationOptions:UITableViewRowAnimationNone]; + [self endUpdates]; } #pragma mark - @@ -681,53 +816,41 @@ - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)n - (void)beginAdjustingContentOffset { - ASDisplayNodeAssert(_automaticallyAdjustsContentOffset, @"this method should only be called when _automaticallyAdjustsContentOffset == YES"); - _contentOffsetAdjustment = 0; - _contentOffsetAdjustmentTopVisibleRow = self.indexPathsForVisibleRows.firstObject; + NSIndexPath *firstVisibleIndexPath = [self.indexPathsForVisibleRows sortedArrayUsingSelector:@selector(compare:)].firstObject; + if (firstVisibleIndexPath) { + ASCellNode *node = [self nodeForRowAtIndexPath:firstVisibleIndexPath]; + if (node) { + _contentOffsetAdjustmentTopVisibleNode = node; + _contentOffsetAdjustmentTopVisibleNodeOffset = [self rectForRowAtIndexPath:firstVisibleIndexPath].origin.y - self.bounds.origin.y; + } + } } -- (void)endAdjustingContentOffset +- (void)endAdjustingContentOffsetAnimated:(BOOL)animated { - ASDisplayNodeAssert(_automaticallyAdjustsContentOffset, @"this method should only be called when _automaticallyAdjustsContentOffset == YES"); - if (_contentOffsetAdjustment != 0) { - self.contentOffset = CGPointMake(0, self.contentOffset.y+_contentOffsetAdjustment); + // We can't do this for animated updates. + if (animated) { + return; } - - _contentOffsetAdjustment = 0; - _contentOffsetAdjustmentTopVisibleRow = nil; -} - -- (void)adjustContentOffsetWithNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths inserting:(BOOL)inserting { - // Maintain the users visible window when inserting or deleting cells by adjusting the content offset for nodes - // before the visible area. If in a begin/end updates block this will update _contentOffsetAdjustment, otherwise it will - // update self.contentOffset directly. - - ASDisplayNodeAssert(_automaticallyAdjustsContentOffset, @"this method should only be called when _automaticallyAdjustsContentOffset == YES"); - - CGFloat dir = (inserting) ? +1 : -1; - CGFloat adjustment = 0; - NSIndexPath *top = _contentOffsetAdjustmentTopVisibleRow ? : self.indexPathsForVisibleRows.firstObject; - - for (int index = 0; index < indexPaths.count; index++) { - NSIndexPath *indexPath = indexPaths[index]; - if ([indexPath compare:top] <= 0) { // if this row is before or equal to the topmost visible row, make adjustments... - ASCellNode *cellNode = nodes[index]; - adjustment += cellNode.calculatedSize.height * dir; - if (indexPath.section == top.section) { - top = [NSIndexPath indexPathForRow:top.row+dir inSection:top.section]; - } - } + + // We can't do this if we didn't have a top visible row before. + if (_contentOffsetAdjustmentTopVisibleNode == nil) { + return; } - - if (_contentOffsetAdjustmentTopVisibleRow) { // true of we are in a begin/end update block (see beginAdjustingContentOffset) - _contentOffsetAdjustmentTopVisibleRow = top; - _contentOffsetAdjustment += adjustment; - } else if (adjustment != 0) { - self.contentOffset = CGPointMake(0, self.contentOffset.y+adjustment); + + NSIndexPath *newIndexPathForTopVisibleRow = [self indexPathForNode:_contentOffsetAdjustmentTopVisibleNode]; + // We can't do this if our top visible row was deleted + if (newIndexPathForTopVisibleRow == nil) { + return; } + + CGFloat newRowOriginYInSelf = [self rectForRowAtIndexPath:newIndexPathForTopVisibleRow].origin.y - self.bounds.origin.y; + CGPoint newContentOffset = self.contentOffset; + newContentOffset.y += (newRowOriginYInSelf - _contentOffsetAdjustmentTopVisibleNodeOffset); + self.contentOffset = newContentOffset; + _contentOffsetAdjustmentTopVisibleNode = nil; } - #pragma mark - Intercepted selectors - (void)setTableHeaderView:(UIView *)tableHeaderView @@ -751,18 +874,11 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N _ASTableViewCell *cell = [self dequeueReusableCellWithIdentifier:kCellReuseIdentifier forIndexPath:indexPath]; cell.delegate = self; - ASCellNode *node = [_dataController nodeAtCompletedIndexPath:indexPath]; + ASCellNode *node = [_dataController.visibleMap elementForItemAtIndexPath:indexPath].node; if (node) { [_rangeController configureContentView:cell.contentView forCellNode:node]; cell.node = node; - cell.backgroundColor = node.backgroundColor; - cell.selectionStyle = node.selectionStyle; - - // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) - // This is actually a workaround for a bug we are seeing in some rare cases (selected background view - // overlaps other cells if size of ASCellNode has changed.) - cell.clipsToBounds = node.clipsToBounds; } return cell; @@ -770,18 +886,29 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; - return node.calculatedSize.height; + ASCellNode *node = [_dataController.visibleMap elementForItemAtIndexPath:indexPath].node; + CGFloat height = node.calculatedSize.height; + + /** + * Weirdly enough, Apple expects the return value here to _include_ the height + * of the separator, if there is one! So if our node wants to be 43.5, we need + * to return 44. UITableView will make a cell of height 44 with a content view + * of height 43.5. + */ + if (tableView.separatorStyle != UITableViewCellSeparatorStyleNone) { + height += 1.0 / ASScreenScale(); + } + return height; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return [_dataController completedNumberOfSections]; + return _dataController.visibleMap.numberOfSections; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return [_dataController completedNumberOfRowsInSection:section]; + return [_dataController.visibleMap numberOfItemsInSection:section]; } - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath @@ -798,14 +925,22 @@ - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sou if (_asyncDataSourceFlags.tableViewMoveRow) { [_asyncDataSource tableView:self moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; } + // Move node after informing data source in case they call nodeAtIndexPath: - [_dataController moveCompletedNodeAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; + // Get up to date + [self waitUntilAllUpdatesAreCommitted]; + // Set our flag to suppress informing super about the change. + _updatingInResponseToInteractiveMove = YES; + // Submit the move + [self moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; + // Wait for it to finish – should be fast! + [self waitUntilAllUpdatesAreCommitted]; + // Clear the flag + _updatingInResponseToInteractiveMove = NO; } - (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { - _pendingVisibleIndexPath = indexPath; - ASCellNode *cellNode = [cell node]; cellNode.scrollView = tableView; @@ -832,10 +967,6 @@ - (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)c - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { - if (ASObjectIsEqual(_pendingVisibleIndexPath, indexPath)) { - _pendingVisibleIndexPath = nil; - } - ASCellNode *cellNode = [cell node]; [_rangeController setNeedsUpdate]; @@ -1039,6 +1170,7 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; if (ASInterfaceStateIncludesVisible(interfaceState)) { [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; + [self _checkForBatchFetching]; } for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) { @@ -1061,7 +1193,7 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi if (targetContentOffset != NULL) { ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; + [self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset]; } if (_asyncDelegateFlags.scrollViewWillEndDragging) { @@ -1069,6 +1201,15 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi } } +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView +{ + _deceleratingVelocity = CGPointZero; + + if (_asyncDelegateFlags.scrollViewDidEndDecelerating) { + [_asyncDelegate scrollViewDidEndDecelerating:scrollView]; + } +} + - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) { @@ -1167,9 +1308,10 @@ - (BOOL)canBatchFetch - (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes { // Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided - if (changes == 0) { + if (changes == 0 && _hasEverCheckedForBatchFetchingDueToUpdate) { return; } + _hasEverCheckedForBatchFetchingDueToUpdate = YES; // Push this to the next runloop to be sure the scroll view has the right content size dispatch_async(dispatch_get_main_queue(), ^{ @@ -1184,12 +1326,12 @@ - (void)_checkForBatchFetching return; } - [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; + [self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset]; } -- (void)_beginBatchFetchingIfNeededWithScrollView:(UIScrollView *)scrollView forScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset +- (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset { - if (ASDisplayShouldFetchBatchForScrollView(self, scrollDirection, contentOffset)) { + if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, ASScrollDirectionVerticalDirections, contentOffset)) { [self _beginBatchFetching]; } } @@ -1219,41 +1361,33 @@ - (ASRangeController *)rangeController return _rangeController; } -- (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController +- (NSArray *)visibleElementsForRangeController:(ASRangeController *)rangeController { ASDisplayNodeAssertMainThread(); + CGRect bounds = self.bounds; // Calling indexPathsForVisibleRows will trigger UIKit to call reloadData if it never has, which can result // in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast. - if (CGSizeEqualToSize(self.bounds.size, CGSizeZero)) { + if (CGRectIsEmpty(bounds)) { return @[]; } - - // NOTE: A prior comment claimed that `indexPathsForVisibleRows` may return extra index paths for grouped-style - // tables. This is seen as an acceptable issue for the time being. - - NSIndexPath *pendingVisibleIndexPath = _pendingVisibleIndexPath; - if (pendingVisibleIndexPath == nil) { - return self.indexPathsForVisibleRows; - } - - NSMutableArray *visibleIndexPaths = [self.indexPathsForVisibleRows mutableCopy]; - [visibleIndexPaths sortUsingSelector:@selector(compare:)]; - BOOL isPendingIndexPathVisible = (NSNotFound != [visibleIndexPaths indexOfObject:pendingVisibleIndexPath inSortedRange:NSMakeRange(0, visibleIndexPaths.count) options:kNilOptions usingComparator:^(id _Nonnull obj1, id _Nonnull obj2) { - return [obj1 compare:obj2]; - }]); - - if (isPendingIndexPathVisible) { - _pendingVisibleIndexPath = nil; // once it has shown up in visibleIndexPaths, we can stop tracking it - } else if ([self isIndexPath:visibleIndexPaths.firstObject immediateSuccessorOfIndexPath:pendingVisibleIndexPath]) { - [visibleIndexPaths insertObject:pendingVisibleIndexPath atIndex:0]; - } else if ([self isIndexPath:pendingVisibleIndexPath immediateSuccessorOfIndexPath:visibleIndexPaths.lastObject]) { - [visibleIndexPaths addObject:pendingVisibleIndexPath]; - } else { - _pendingVisibleIndexPath = nil; // not contiguous, ignore. + NSArray *visibleIndexPaths = self.indexPathsForVisibleRows; + + // In some cases (grouped-style tables with particular geometry) indexPathsForVisibleRows will return extra index paths. + // This is a very serious issue because we rely on the fact that any node that is marked Visible is hosted inside of a cell, + // or else we may not mark it invisible before the node is released. See testIssue2252. + // Calling indexPathForCell: and cellForRowAtIndexPath: are both pretty expensive – this is the quickest approach we have. + // It would be possible to cache this NSPredicate as an ivar, but that would require unsafeifying self and calling @c bounds + // for each item. Since the performance cost is pretty small, prefer simplicity. + if (self.style == UITableViewStyleGrouped && visibleIndexPaths.count != self.visibleCells.count) { + visibleIndexPaths = [visibleIndexPaths filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSIndexPath *indexPath, NSDictionary * _Nullable bindings) { + return CGRectIntersectsRect(bounds, [self rectForRowAtIndexPath:indexPath]); + }]]; } - return visibleIndexPaths; + + ASElementMap *map = _dataController.visibleMap; + return ASArrayByFlatMapping(visibleIndexPaths, NSIndexPath *indexPath, [map elementForItemAtIndexPath:indexPath]); } - (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController @@ -1261,17 +1395,6 @@ - (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rang return self.scrollDirection; } -- (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath -{ - return [_dataController nodeAtIndexPath:indexPath]; -} - -- (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController -{ - ASDisplayNodeAssertMainThread(); - return self.bounds.size; -} - - (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController { return ASInterfaceStateForDisplayNode(self.tableNode, self.window); @@ -1284,153 +1407,155 @@ - (NSString *)nameForRangeControllerDataSource #pragma mark - ASRangeControllerDelegate -- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController +- (void)rangeController:(ASRangeController *)rangeController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet { ASDisplayNodeAssertMainThread(); - LOG(@"--- UITableView beginUpdates"); - if (!self.asyncDataSource) { return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - - _performingBatchUpdates = YES; - [super beginUpdates]; - - if (_automaticallyAdjustsContentOffset) { + + if (_automaticallyAdjustsContentOffset && !changeSet.includesReloadData) { [self beginAdjustingContentOffset]; } } -- (void)rangeController:(ASRangeController *)rangeController didEndUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion +- (void)rangeController:(ASRangeController *)rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet { ASDisplayNodeAssertMainThread(); - LOG(@"--- UITableView endUpdates"); - - if (!self.asyncDataSource) { - if (completion) { - completion(NO); - } + if (!self.asyncDataSource || _updatingInResponseToInteractiveMove) { + [changeSet executeCompletionHandlerWithFinished:NO]; return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - - if (_automaticallyAdjustsContentOffset) { - [self endAdjustingContentOffset]; + + if (changeSet.includesReloadData) { + LOG(@"UITableView reloadData"); + ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ + if (self.test_enableSuperUpdateCallLogging) { + NSLog(@"-[super reloadData]"); + } + [super reloadData]; + // Flush any range changes that happened as part of submitting the reload. + [_rangeController updateIfNeeded]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:1]; + [changeSet executeCompletionHandlerWithFinished:YES]; + }); + return; } - - ASPerformBlockWithoutAnimation(!animated, ^{ - [super endUpdates]; - [_rangeController updateIfNeeded]; - }); - - _performingBatchUpdates = NO; - if (completion) { - completion(YES); + + NSUInteger numberOfUpdates = 0; + + LOG(@"--- UITableView beginUpdates"); + [super beginUpdates]; + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { + NSArray *indexPaths = change.indexPaths; + UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; + + LOG(@"UITableView reloadRows:%ld rows", indexPaths.count); + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + if (self.test_enableSuperUpdateCallLogging) { + NSLog(@"-[super reloadRowsAtIndexPaths]: %@", indexPaths); + } + [super reloadRowsAtIndexPaths:indexPaths withRowAnimation:animationOptions]; + }); + + numberOfUpdates++; } -} - -- (void)didCompleteUpdatesInRangeController:(ASRangeController *)rangeController -{ - [self _checkForBatchFetching]; -} - -- (void)rangeController:(ASRangeController *)rangeController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView insertRows:%ld rows", indexPaths.count); - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { + NSIndexSet *sectionIndexes = change.indexSet; + UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; + + LOG(@"UITableView reloadSections:%@", sectionIndexes); + BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + if (self.test_enableSuperUpdateCallLogging) { + NSLog(@"-[super reloadSections]: %@", sectionIndexes); + } + [super reloadSections:sectionIndexes withRowAnimation:animationOptions]; + }); + + numberOfUpdates++; } - - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super insertRowsAtIndexPaths]: %@", indexPaths); - } - [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - if (!_performingBatchUpdates) { - [_rangeController updateIfNeeded]; - } - [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; - }); - - if (_automaticallyAdjustsContentOffset) { - [self adjustContentOffsetWithNodes:nodes atIndexPaths:indexPaths inserting:YES]; + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { + NSArray *indexPaths = change.indexPaths; + UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; + + LOG(@"UITableView deleteRows:%ld rows", indexPaths.count); + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + if (self.test_enableSuperUpdateCallLogging) { + NSLog(@"-[super deleteRowsAtIndexPaths]: %@", indexPaths); + } + [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:animationOptions]; + }); + + numberOfUpdates++; } -} - -- (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView deleteRows:%ld rows", indexPaths.count); - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { + NSIndexSet *sectionIndexes = change.indexSet; + UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; + + LOG(@"UITableView deleteSections:%@", sectionIndexes); + BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + if (self.test_enableSuperUpdateCallLogging) { + NSLog(@"-[super deleteSections]: %@", sectionIndexes); + } + [super deleteSections:sectionIndexes withRowAnimation:animationOptions]; + }); + + numberOfUpdates++; } - - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super deleteRowsAtIndexPaths]: %@", indexPaths); - } - [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - if (!_performingBatchUpdates) { - [_rangeController updateIfNeeded]; - } - [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; - }); - - if (_automaticallyAdjustsContentOffset) { - [self adjustContentOffsetWithNodes:nodes atIndexPaths:indexPaths inserting:NO]; + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { + NSIndexSet *sectionIndexes = change.indexSet; + UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; + + LOG(@"UITableView insertSections:%@", sectionIndexes); + BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + if (self.test_enableSuperUpdateCallLogging) { + NSLog(@"-[super insertSections]: %@", sectionIndexes); + } + [super insertSections:sectionIndexes withRowAnimation:animationOptions]; + }); + + numberOfUpdates++; } -} - -- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView insertSections:%@", indexSet); - - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { + NSArray *indexPaths = change.indexPaths; + UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; + + LOG(@"UITableView insertRows:%ld rows", indexPaths.count); + BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + if (self.test_enableSuperUpdateCallLogging) { + NSLog(@"-[super insertRowsAtIndexPaths]: %@", indexPaths); + } + [super insertRowsAtIndexPaths:indexPaths withRowAnimation:animationOptions]; + }); + + numberOfUpdates++; } - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super insertSections]: %@", indexSet); - } - [super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; - if (!_performingBatchUpdates) { - [_rangeController updateIfNeeded]; - } - [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; + LOG(@"--- UITableView endUpdates"); + ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ + [super endUpdates]; + [_rangeController updateIfNeeded]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates]; }); -} - -- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView deleteSections:%@", indexSet); - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + if (_automaticallyAdjustsContentOffset) { + [self endAdjustingContentOffsetAnimated:changeSet.animated]; } - - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super deleteSections]: %@", indexSet); - } - [super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; - if (!_performingBatchUpdates) { - [_rangeController updateIfNeeded]; - } - [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; - }); + [changeSet executeCompletionHandlerWithFinished:YES]; } -#pragma mark - ASDataControllerDelegate +#pragma mark - ASDataControllerSource - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { ASCellNodeBlock block = nil; @@ -1484,6 +1609,9 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAt if (node.interactionDelegate == nil) { node.interactionDelegate = strongSelf; } + if (_inverted) { + node.transform = CATransform3DMakeScale(1, -1, 1) ; + } return node; }; return block; @@ -1491,7 +1619,7 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAt - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - ASSizeRange constrainedSize = kInvalidSizeRange; + ASSizeRange constrainedSize = ASSizeRangeZero; if (_asyncDelegateFlags.tableNodeConstrainedSizeForRow) { GET_TABLENODE_OR_RETURN(tableNode, constrainedSize); ASSizeRange delegateConstrainedSize = [_asyncDelegate tableNode:tableNode constrainedSizeForRowAtIndexPath:indexPath]; @@ -1543,9 +1671,25 @@ - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataControlle } } +- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size +{ + NSIndexPath *indexPath = [self indexPathForNode:element.node]; + CGRect rect = [self rectForRowAtIndexPath:indexPath]; + + /** + * Weirdly enough, Apple expects the return value in tableView:heightForRowAtIndexPath: to _include_ the height + * of the separator, if there is one! So if rectForRow would return 44.0 we need to use 43.5. + */ + if (self.separatorStyle != UITableViewCellSeparatorStyleNone) { + rect.size.height -= 1.0 / ASScreenScale(); + } + + return (fabs(rect.size.height - size.height) < FLT_EPSILON); +} + #pragma mark - ASDataControllerEnvironmentDelegate -- (id)dataControllerEnvironment +- (id)dataControllerEnvironment { return self.tableNode; } @@ -1583,7 +1727,7 @@ - (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell // If the node height changed, trigger a height requery. if (oldSize.height != calculatedSize.height) { [self beginUpdates]; - [self endUpdates]; + [self endUpdatesAnimated:(ASDisplayNodeLayerHasAnimations(self.layer) == NO) completion:nil]; } } } @@ -1592,7 +1736,7 @@ - (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell - (void)nodeSelectedStateDidChange:(ASCellNode *)node { - NSIndexPath *indexPath = [_dataController completedIndexPathForNode:node]; + NSIndexPath *indexPath = [self indexPathForNode:node]; if (indexPath) { if (node.isSelected) { [self selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; @@ -1604,17 +1748,23 @@ - (void)nodeSelectedStateDidChange:(ASCellNode *)node - (void)nodeHighlightedStateDidChange:(ASCellNode *)node { - NSIndexPath *indexPath = [_dataController completedIndexPathForNode:node]; + NSIndexPath *indexPath = [self indexPathForNode:node]; if (indexPath) { [self cellForRowAtIndexPath:indexPath].highlighted = node.isHighlighted; } } +- (void)nodeDidInvalidateSize:(ASCellNode *)node +{ + [_cellsForLayoutUpdates addObject:node]; + [self setNeedsLayout]; +} + - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged { ASDisplayNodeAssertMainThread(); - if (!sizeChanged || _queuedNodeHeightUpdate) { + if (!sizeChanged || _queuedNodeHeightUpdate || _remeasuringCellNodes) { return; } @@ -1634,18 +1784,6 @@ - (void)requeryNodeHeights [super endUpdates]; } -#pragma mark - Memory Management - -- (void)clearContents -{ - [_rangeController clearContents]; -} - -- (void)clearFetchedData -{ - [_rangeController clearFetchedData]; -} - #pragma mark - Helper Methods // Note: This is called every layout, and so it is very performance sensitive. @@ -1673,31 +1811,6 @@ - (CGFloat)sectionIndexWidth return 0; } -/// @note This should be a UIKit index path. -- (BOOL)isIndexPath:(NSIndexPath *)indexPath immediateSuccessorOfIndexPath:(NSIndexPath *)anchor -{ - if (!anchor || !indexPath) { - return NO; - } - if (indexPath.section == anchor.section) { - return (indexPath.row == anchor.row+1); // assumes that indexes are valid - - } else if (indexPath.section > anchor.section && indexPath.row == 0) { - if (anchor.row != [_dataController completedNumberOfRowsInSection:anchor.section] -1) { - return NO; // anchor is not at the end of the section - } - - NSInteger nextSection = anchor.section+1; - while([_dataController completedNumberOfRowsInSection:nextSection] == 0) { - ++nextSection; - } - - return indexPath.section == nextSection; - } - - return NO; -} - #pragma mark - _ASDisplayView behavior substitutions // Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. // Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. @@ -1724,6 +1837,12 @@ - (void)didMoveToWindow [_rangeController setNeedsUpdate]; [_rangeController updateIfNeeded]; } + + // When we aren't visible, we will only fetch up to the visible area. Now that we are visible, + // we will fetch visible area + leading screens, so we need to check. + if (visible) { + [self _checkForBatchFetching]; + } } @end diff --git a/AsyncDisplayKit/ASTableViewInternal.h b/Source/ASTableViewInternal.h similarity index 99% rename from AsyncDisplayKit/ASTableViewInternal.h rename to Source/ASTableViewInternal.h index 2dc5533090..014e90114a 100644 --- a/AsyncDisplayKit/ASTableViewInternal.h +++ b/Source/ASTableViewInternal.h @@ -15,6 +15,7 @@ @class ASDataController; @class ASTableNode; @class ASRangeController; +@class ASEventLog; @interface ASTableView (Internal) diff --git a/AsyncDisplayKit/ASTableViewProtocols.h b/Source/ASTableViewProtocols.h similarity index 99% rename from AsyncDisplayKit/ASTableViewProtocols.h rename to Source/ASTableViewProtocols.h index 00719dea26..28538b20e4 100644 --- a/AsyncDisplayKit/ASTableViewProtocols.h +++ b/Source/ASTableViewProtocols.h @@ -8,6 +8,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import + NS_ASSUME_NONNULL_BEGIN /** diff --git a/AsyncDisplayKit/ASTextNode+Beta.h b/Source/ASTextNode+Beta.h similarity index 95% rename from AsyncDisplayKit/ASTextNode+Beta.h rename to Source/ASTextNode+Beta.h index e5d34678cc..a20bac8625 100644 --- a/AsyncDisplayKit/ASTextNode+Beta.h +++ b/Source/ASTextNode+Beta.h @@ -10,6 +10,10 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import + +#import + NS_ASSUME_NONNULL_BEGIN @interface ASTextNode () diff --git a/AsyncDisplayKit/ASTextNode.h b/Source/ASTextNode.h similarity index 100% rename from AsyncDisplayKit/ASTextNode.h rename to Source/ASTextNode.h diff --git a/AsyncDisplayKit/ASTextNode.mm b/Source/ASTextNode.mm similarity index 79% rename from AsyncDisplayKit/ASTextNode.mm rename to Source/ASTextNode.mm index 9eb503a13e..bd09de7c86 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -8,27 +8,27 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextNode.h" -#import "ASTextNode+Beta.h" +#import +#import #include #import -#import "_ASDisplayLayer.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNodeInternal.h" -#import "ASHighlightOverlayLayer.h" -#import "ASDisplayNodeExtras.h" +#import +#import +#import +#import -#import "ASTextKitCoreTextAdditions.h" -#import "ASTextKitRenderer+Positioning.h" -#import "ASTextKitShadower.h" +#import +#import +#import -#import "ASInternalHelpers.h" -#import "ASLayout.h" +#import +#import -#import "CoreGraphics+ASConvenience.h" -#import "ASObjectDescriptionHelpers.h" +#import +#import +#import /** * If set, we will record all values set to attributedText into an array @@ -50,6 +50,65 @@ UIColor *backgroundColor; }; +#pragma mark - ASTextKitRenderer + +@interface ASTextNodeRendererKey : NSObject +@property (assign, nonatomic) ASTextKitAttributes attributes; +@property (assign, nonatomic) CGSize constrainedSize; +@end + +@implementation ASTextNodeRendererKey + +- (NSUInteger)hash +{ + return _attributes.hash() ^ ASHashFromCGSize(_constrainedSize); +} + +- (BOOL)isEqual:(ASTextNodeRendererKey *)object +{ + if (self == object) { + return YES; + } + + return _attributes == object.attributes && CGSizeEqualToSize(_constrainedSize, object.constrainedSize); +} + +@end + +static NSCache *sharedRendererCache() +{ + static dispatch_once_t onceToken; + static NSCache *__rendererCache = nil; + dispatch_once(&onceToken, ^{ + __rendererCache = [[NSCache alloc] init]; + __rendererCache.countLimit = 500; // 500 renders cache + }); + return __rendererCache; +} + +/** + The concept here is that neither the node nor layout should ever have a strong reference to the renderer object. + This is to reduce memory load when loading thousands and thousands of text nodes into memory at once. Instead + we maintain a LRU renderer cache that is queried via a unique key based on text kit attributes and constrained size. + */ + +static ASTextKitRenderer *rendererForAttributes(ASTextKitAttributes attributes, CGSize constrainedSize) +{ + NSCache *cache = sharedRendererCache(); + + ASTextNodeRendererKey *key = [[ASTextNodeRendererKey alloc] init]; + key.attributes = attributes; + key.constrainedSize = constrainedSize; + + ASTextKitRenderer *renderer = [cache objectForKey:key]; + if (renderer == nil) { + renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:attributes constrainedSize:constrainedSize]; + [cache setObject:renderer forKey:key]; + } + + return renderer; +} + @interface ASTextNode () @end @@ -65,6 +124,7 @@ @implementation ASTextNode { NSArray *_exclusionPaths; + NSAttributedString *_attributedText; NSAttributedString *_composedTruncationText; NSString *_highlightedLinkAttributeName; @@ -73,10 +133,6 @@ @implementation ASTextNode { NSRange _highlightRange; ASHighlightOverlayLayer *_activeHighlightLayer; - CGSize _constrainedSize; - - ASTextKitRenderer *_renderer; - ASTextNodeDrawParameter _drawParameter; UILongPressGestureRecognizer *_longPressGestureRecognizer; @@ -111,7 +167,6 @@ - (instancetype)init self.needsDisplayOnBoundsChange = YES; _truncationMode = NSLineBreakByWordWrapping; - _composedTruncationText = DefaultTruncationAttributedString(); // The common case is for a text node to be non-opaque and blended over some background. self.opaque = NO; @@ -123,8 +178,6 @@ - (instancetype)init self.isAccessibilityElement = YES; self.accessibilityTraits = UIAccessibilityTraitStaticText; - _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); - // Placeholders // Disabled by default in ASDisplayNode, but add a few options for those who toggle // on the special placeholder behavior of ASTextNode. @@ -139,8 +192,6 @@ - (void)dealloc { CGColorRelease(_shadowColor); - [self _invalidateRenderer]; - if (_longPressGestureRecognizer) { _longPressGestureRecognizer.delegate = nil; [_longPressGestureRecognizer removeTarget:nil action:NULL]; @@ -181,27 +232,12 @@ - (NSString *)_plainStringForDescription #pragma mark - ASDisplayNode -// FIXME: Re-evaluate if it is still the right decision to clear the renderer at this stage. -// This code was written before TextKit and when 512MB devices were still the overwhelming majority. -- (void)displayDidFinish -{ - [super displayDidFinish]; - - // We invalidate our renderer here to clear the very high memory cost of - // keeping this around. _invalidateRenderer will dealloc this onto a bg - // thread resulting in less stutters on the main thread than if it were - // to be deallocated in dealloc. This is also helpful in opportunistically - // reducing memory consumption and reducing the overall footprint of the app. - [self _invalidateRenderer]; -} - - (void)clearContents { // We discard the backing store and renderer to prevent the very large // memory overhead of maintaining these for all text nodes. They can be // regenerated when layout is necessary. [super clearContents]; // ASDisplayNode will set layer.contents = nil - [self _invalidateRenderer]; } - (void)didLoad @@ -218,52 +254,30 @@ - (void)didLoad } } -- (void)setFrame:(CGRect)frame -{ - [super setFrame:frame]; - [self _invalidateRendererIfNeededForBoundsSize:frame.size]; -} - -- (void)setBounds:(CGRect)bounds -{ - [super setBounds:bounds]; - [self _invalidateRendererIfNeededForBoundsSize:bounds.size]; -} - #pragma mark - Renderer Management - (ASTextKitRenderer *)_renderer { - return [self _rendererWithBounds:self.threadSafeBounds]; + CGSize constrainedSize = self.threadSafeBounds.size; + return [self _rendererWithBoundsSlow:{.size = constrainedSize}]; } -- (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds +- (ASTextKitRenderer *)_rendererWithBoundsSlow:(CGRect)bounds { ASDN::MutexLocker l(__instanceLock__); - - if (_renderer == nil) { - CGSize constrainedSize; - if (_constrainedSize.width != -INFINITY) { - constrainedSize = _constrainedSize; - } else { - constrainedSize = bounds.size; - constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right); - constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom); - } - - _renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes] - constrainedSize:constrainedSize]; - } - return _renderer; + bounds.size.width -= (_textContainerInset.left + _textContainerInset.right); + bounds.size.height -= (_textContainerInset.top + _textContainerInset.bottom); + return rendererForAttributes([self _rendererAttributes], bounds.size); } + - (ASTextKitAttributes)_rendererAttributes { ASDN::MutexLocker l(__instanceLock__); return { .attributedString = _attributedText, - .truncationAttributedString = _composedTruncationText, + .truncationAttributedString = [self _locked_composedTruncationText], .lineBreakMode = _truncationMode, .maximumNumberOfLines = _maximumNumberOfLines, .exclusionPaths = _exclusionPaths, @@ -276,47 +290,18 @@ - (ASTextKitAttributes)_rendererAttributes }; } -- (void)_invalidateRendererIfNeeded -{ - [self _invalidateRendererIfNeededForBoundsSize:self.threadSafeBounds.size]; -} - -- (void)_invalidateRendererIfNeededForBoundsSize:(CGSize)boundsSize -{ - if ([self _needInvalidateRendererForBoundsSize:boundsSize]) { - // Our bounds have changed to a size that is not identical to our constraining size, - // so our previous layout information is invalid, and TextKit may draw at the - // incorrect origin. - { - ASDN::MutexLocker l(__instanceLock__); - _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); - } - [self _invalidateRenderer]; - } -} - -- (void)_invalidateRenderer -{ - ASDN::MutexLocker l(__instanceLock__); - - if (_renderer) { - // Destruction of the layout managers/containers/text storage is quite - // expensive, and can take some time, so we dispatch onto a bg queue to - // actually dealloc. - ASPerformBackgroundDeallocation(_renderer); - _renderer = nil; - } -} - #pragma mark - Layout and Sizing - (void)setTextContainerInset:(UIEdgeInsets)textContainerInset { - ASDN::MutexLocker l(__instanceLock__); - - BOOL needsUpdate = !UIEdgeInsetsEqualToEdgeInsets(textContainerInset, _textContainerInset); + __instanceLock__.lock(); + BOOL needsUpdate = !UIEdgeInsetsEqualToEdgeInsets(textContainerInset, _textContainerInset); + if (needsUpdate) { + _textContainerInset = textContainerInset; + } + __instanceLock__.unlock(); + if (needsUpdate) { - _textContainerInset = textContainerInset; [self setNeedsLayout]; } } @@ -327,60 +312,6 @@ - (UIEdgeInsets)textContainerInset return _textContainerInset; } -- (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize -{ - ASDN::MutexLocker l(__instanceLock__); - - if (_renderer == nil) { - return YES; - } - - // If the size is not the same as the constraint we provided to the renderer, start out assuming we need - // a new one. However, there are common cases where the constrained size doesn't need to be the same as calculated. - CGSize rendererConstrainedSize = _renderer.constrainedSize; - - //inset bounds - boundsSize.width -= _textContainerInset.left + _textContainerInset.right; - boundsSize.height -= _textContainerInset.top + _textContainerInset.bottom; - - if (CGSizeEqualToSize(boundsSize, rendererConstrainedSize)) { - return NO; - } else { - // It is very common to have a constrainedSize with a concrete, specific width but +Inf height. - // In this case, as long as the text node has bounds as large as the full calculatedLayout suggests, - // it means that the text has all the room it needs (as it was not vertically bounded). So, we will not - // experience truncation and don't need to recreate the renderer with the size it already calculated, - // as this would essentially serve to set its constrainedSize to be its calculatedSize (unnecessary). - ASLayout *layout = self.calculatedLayout; - if (layout != nil && CGSizeEqualToSize(boundsSize, layout.size)) { - return (boundsSize.width != rendererConstrainedSize.width); - } else { - return YES; - } - } -} - -- (void)calculatedLayoutDidChange -{ - [super calculatedLayoutDidChange]; - - ASLayout *layout = self.calculatedLayout; - - if (layout != nil) { - ASDN::MutexLocker l(__instanceLock__); - CGSize layoutSize = layout.size; - - // Apply textContainerInset - layoutSize.width -= (_textContainerInset.left + _textContainerInset.right); - layoutSize.height -= (_textContainerInset.top + _textContainerInset.bottom); - - if (CGSizeEqualToSize(_constrainedSize, layoutSize) == NO) { - _constrainedSize = layoutSize; - [self _invalidateRenderer]; - } - } -} - - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { ASDN::MutexLocker l(__instanceLock__); @@ -390,27 +321,18 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize // Cache the original constrained size for final size calculateion CGSize originalConstrainedSize = constrainedSize; - - // Adjust constrainedSize for textContainerInset before assigning it - constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right); - constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom); - - _constrainedSize = constrainedSize; - - if (_renderer != nil && CGSizeEqualToSize(constrainedSize, _renderer.constrainedSize) == NO) { - [self _invalidateRenderer]; - } [self setNeedsDisplay]; - CGSize size = [self _renderer].size; + ASTextKitRenderer *renderer = [self _rendererWithBoundsSlow:{.size = constrainedSize}]; + CGSize size = renderer.size; if (_attributedText.length > 0) { self.style.ascender = [[self class] ascenderWithAttributedString:_attributedText]; self.style.descender = [[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender]; - if (_renderer.currentScaleFactor > 0 && _renderer.currentScaleFactor < 1.0) { + if (renderer.currentScaleFactor > 0 && renderer.currentScaleFactor < 1.0) { // while not perfect, this is a good estimate of what the ascender of the scaled font will be. - self.style.ascender *= _renderer.currentScaleFactor; - self.style.descender *= _renderer.currentScaleFactor; + self.style.ascender *= renderer.currentScaleFactor; + self.style.descender *= renderer.currentScaleFactor; } } @@ -439,6 +361,12 @@ + (CGFloat)ascenderWithAttributedString:(NSAttributedString *)attributedString return lineHeight + font.descender; } +- (NSAttributedString *)attributedText +{ + ASDN::MutexLocker l(__instanceLock__); + return _attributedText; +} + - (void)setAttributedText:(NSAttributedString *)attributedText { @@ -457,14 +385,10 @@ - (void)setAttributedText:(NSAttributedString *)attributedText #if AS_TEXTNODE_RECORD_ATTRIBUTED_STRINGS [ASTextNode _registerAttributedText:_attributedText]; #endif - // Sync the truncation string with attributes from the updated _attributedString - // Without this, the size calculation of the text with truncation applied will - // not take into account the attributes of attributedText in the last line - [self _updateComposedTruncationText]; - - // We need an entirely new renderer - [self _invalidateRenderer]; } + + // Since truncation text matches style of attributedText, invalidate it now. + [self _invalidateTruncationText]; NSUInteger length = attributedText.length; if (length > 0) { @@ -488,14 +412,16 @@ - (void)setAttributedText:(NSAttributedString *)attributedText - (void)setExclusionPaths:(NSArray *)exclusionPaths { - ASDN::MutexLocker l(__instanceLock__); + { + ASDN::MutexLocker l(__instanceLock__); - if (ASObjectIsEqual(exclusionPaths, _exclusionPaths)) { - return; + if (ASObjectIsEqual(exclusionPaths, _exclusionPaths)) { + return; + } + + _exclusionPaths = [exclusionPaths copy]; } - _exclusionPaths = [exclusionPaths copy]; - [self _invalidateRenderer]; [self setNeedsLayout]; [self setNeedsDisplay]; } @@ -536,7 +462,7 @@ - (void)drawRect:(CGRect)bounds withParameters:(id )p isCancelled:(asd CGContextTranslateCTM(context, _textContainerInset.left, _textContainerInset.top); - ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds]; + ASTextKitRenderer *renderer = [self _rendererWithBoundsSlow:drawParameterBounds]; // Fill background if (backgroundColor != nil) { @@ -790,11 +716,12 @@ - (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *) if (highlightTargetLayer != nil) { ASDN::MutexLocker l(__instanceLock__); + ASTextKitRenderer *renderer = [self _renderer]; - NSArray *highlightRects = [[self _renderer] rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock]; + NSArray *highlightRects = [renderer rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock]; NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count]; for (NSValue *rectValue in highlightRects) { - UIEdgeInsets shadowPadding = _renderer.shadower.shadowPadding; + UIEdgeInsets shadowPadding = renderer.shadower.shadowPadding; CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.CGRectValue, shadowPadding); // The rects returned from renderer don't have `textContainerInset`, @@ -1113,15 +1040,19 @@ - (CGColorRef)shadowColor - (void)setShadowColor:(CGColorRef)shadowColor { - ASDN::MutexLocker l(__instanceLock__); - + __instanceLock__.lock(); + if (_shadowColor != shadowColor && CGColorEqualToColor(shadowColor, _shadowColor) == NO) { CGColorRelease(_shadowColor); _shadowColor = CGColorRetain(shadowColor); _cachedShadowUIColor = [UIColor colorWithCGColor:shadowColor]; - [self _invalidateRenderer]; + __instanceLock__.unlock(); + [self setNeedsDisplay]; + return; } + + __instanceLock__.unlock(); } - (CGSize)shadowOffset @@ -1133,13 +1064,16 @@ - (CGSize)shadowOffset - (void)setShadowOffset:(CGSize)shadowOffset { - ASDN::MutexLocker l(__instanceLock__); - - if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) { + { + ASDN::MutexLocker l(__instanceLock__); + + if (CGSizeEqualToSize(_shadowOffset, shadowOffset)) { + return; + } _shadowOffset = shadowOffset; - [self _invalidateRenderer]; - [self setNeedsDisplay]; } + + [self setNeedsDisplay]; } - (CGFloat)shadowOpacity @@ -1151,13 +1085,17 @@ - (CGFloat)shadowOpacity - (void)setShadowOpacity:(CGFloat)shadowOpacity { - ASDN::MutexLocker l(__instanceLock__); - - if (_shadowOpacity != shadowOpacity) { + { + ASDN::MutexLocker l(__instanceLock__); + + if (_shadowOpacity == shadowOpacity) { + return; + } + _shadowOpacity = shadowOpacity; - [self _invalidateRenderer]; - [self setNeedsDisplay]; } + + [self setNeedsDisplay]; } - (CGFloat)shadowRadius @@ -1169,13 +1107,17 @@ - (CGFloat)shadowRadius - (void)setShadowRadius:(CGFloat)shadowRadius { - ASDN::MutexLocker l(__instanceLock__); - - if (_shadowRadius != shadowRadius) { + { + ASDN::MutexLocker l(__instanceLock__); + + if (_shadowRadius == shadowRadius) { + return; + } + _shadowRadius = shadowRadius; - [self _invalidateRenderer]; - [self setNeedsDisplay]; } + + [self setNeedsDisplay]; } - (UIEdgeInsets)shadowPadding @@ -1204,37 +1146,47 @@ - (UIEdgeInsets)shadowPaddingWithRenderer:(ASTextKitRenderer *)renderer - (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText { - ASDN::MutexLocker l(__instanceLock__); - - if (ASObjectIsEqual(_truncationAttributedText, truncationAttributedText)) { - return; + { + ASDN::MutexLocker l(__instanceLock__); + + if (ASObjectIsEqual(_truncationAttributedText, truncationAttributedText)) { + return; + } + + _truncationAttributedText = [truncationAttributedText copy]; } - _truncationAttributedText = [truncationAttributedText copy]; [self _invalidateTruncationText]; } - (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage { - ASDN::MutexLocker l(__instanceLock__); - - if (ASObjectIsEqual(_additionalTruncationMessage, additionalTruncationMessage)) { - return; + { + ASDN::MutexLocker l(__instanceLock__); + + if (ASObjectIsEqual(_additionalTruncationMessage, additionalTruncationMessage)) { + return; + } + + _additionalTruncationMessage = [additionalTruncationMessage copy]; } - _additionalTruncationMessage = [additionalTruncationMessage copy]; [self _invalidateTruncationText]; } - (void)setTruncationMode:(NSLineBreakMode)truncationMode { - ASDN::MutexLocker l(__instanceLock__); - - if (_truncationMode != truncationMode) { + { + ASDN::MutexLocker l(__instanceLock__); + + if (_truncationMode == truncationMode) { + return; + } + _truncationMode = truncationMode; - [self _invalidateRenderer]; - [self setNeedsDisplay]; } + + [self setNeedsDisplay]; } - (BOOL)isTruncated @@ -1242,28 +1194,36 @@ - (BOOL)isTruncated ASDN::MutexLocker l(__instanceLock__); ASTextKitRenderer *renderer = [self _renderer]; - return renderer.firstVisibleRange.length < _attributedText.length; + return renderer.isTruncated; } - (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors { - ASDN::MutexLocker l(__instanceLock__); - - if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) { + { + ASDN::MutexLocker l(__instanceLock__); + if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors]) { + return; + } + _pointSizeScaleFactors = pointSizeScaleFactors; - [self _invalidateRenderer]; - [self setNeedsDisplay]; - }} + } + + [self setNeedsDisplay]; +} - (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines { - ASDN::MutexLocker l(__instanceLock__); - - if (_maximumNumberOfLines != maximumNumberOfLines) { + { + ASDN::MutexLocker l(__instanceLock__); + + if (_maximumNumberOfLines == maximumNumberOfLines) { + return; + } + _maximumNumberOfLines = maximumNumberOfLines; - [self _invalidateRenderer]; - [self setNeedsDisplay]; } + + [self setNeedsDisplay]; } - (NSUInteger)lineCount @@ -1275,17 +1235,13 @@ - (NSUInteger)lineCount #pragma mark - Truncation Message -- (void)_updateComposedTruncationText -{ - ASDN::MutexLocker l(__instanceLock__); - - _composedTruncationText = [self _prepareTruncationStringForDrawing:[self _composedTruncationText]]; -} - - (void)_invalidateTruncationText { - [self _updateComposedTruncationText]; - [self _invalidateRenderer]; + { + ASDN::MutexLocker l(__instanceLock__); + _composedTruncationText = nil; + } + [self setNeedsDisplay]; } @@ -1316,29 +1272,24 @@ - (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRan * additional truncation message and a truncation attributed string, they will * be properly composed. */ -- (NSAttributedString *)_composedTruncationText -{ - ASDN::MutexLocker l(__instanceLock__); - - //If we have neither return the default - if (!_additionalTruncationMessage && !_truncationAttributedText) { - return _composedTruncationText; - } - // Short circuit if we only have one or the other. - if (!_additionalTruncationMessage) { - return _truncationAttributedText; - } - if (!_truncationAttributedText) { - return _additionalTruncationMessage; +- (NSAttributedString *)_locked_composedTruncationText +{ + if (_composedTruncationText == nil) { + if (_truncationAttributedText != nil && _additionalTruncationMessage != nil) { + NSMutableAttributedString *newComposedTruncationString = [[NSMutableAttributedString alloc] initWithAttributedString:_truncationAttributedText]; + [newComposedTruncationString.mutableString appendString:@" "]; + [newComposedTruncationString appendAttributedString:_additionalTruncationMessage]; + _composedTruncationText = newComposedTruncationString; + } else if (_truncationAttributedText != nil) { + _composedTruncationText = _truncationAttributedText; + } else if (_additionalTruncationMessage != nil) { + _composedTruncationText = _additionalTruncationMessage; + } else { + _composedTruncationText = DefaultTruncationAttributedString(); + } + _composedTruncationText = [self _locked_prepareTruncationStringForDrawing:_composedTruncationText]; } - - // If we've reached this point, both _additionalTruncationMessage and - // _truncationAttributedText are present. Compose them. - - NSMutableAttributedString *newComposedTruncationString = [[NSMutableAttributedString alloc] initWithAttributedString:_truncationAttributedText]; - [newComposedTruncationString replaceCharactersInRange:NSMakeRange(newComposedTruncationString.length, 0) withString:@" "]; - [newComposedTruncationString appendAttributedString:_additionalTruncationMessage]; - return newComposedTruncationString; + return _composedTruncationText; } /** @@ -1346,10 +1297,8 @@ - (NSAttributedString *)_composedTruncationText * - Adds whole-string attributes so the truncation message matches the styling * of the body text */ -- (NSAttributedString *)_prepareTruncationStringForDrawing:(NSAttributedString *)truncationString +- (NSAttributedString *)_locked_prepareTruncationStringForDrawing:(NSAttributedString *)truncationString { - ASDN::MutexLocker l(__instanceLock__); - truncationString = ASCleanseAttributedStringOfCoreTextAttributes(truncationString); NSMutableAttributedString *truncationMutableString = [truncationString mutableCopy]; // Grab the attributes from the full string diff --git a/AsyncDisplayKit/ASVideoNode.h b/Source/ASVideoNode.h similarity index 92% rename from AsyncDisplayKit/ASVideoNode.h rename to Source/ASVideoNode.h index 501380a10d..63b8334338 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/Source/ASVideoNode.h @@ -8,14 +8,12 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#if TARGET_OS_IOS -#import #import @class AVAsset, AVPlayer, AVPlayerLayer, AVPlayerItem, AVVideoComposition, AVAudioMix; @protocol ASVideoNodeDelegate; -typedef enum { +typedef NS_ENUM(NSInteger, ASVideoNodePlayerState) { ASVideoNodePlayerStateUnknown, ASVideoNodePlayerStateInitialLoading, ASVideoNodePlayerStateReadyToPlay, @@ -24,7 +22,7 @@ typedef enum { ASVideoNodePlayerStateLoading, ASVideoNodePlayerStatePaused, ASVideoNodePlayerStateFinished -} ASVideoNodePlayerState; +}; NS_ASSUME_NONNULL_BEGIN @@ -44,7 +42,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, strong, readwrite) AVAsset *asset; /** ** @abstract The URL with which the asset was initialized. - ** @discussion Setting the URL will overwrite the current asset with a newly created AVURLAsset created from the given URL, and AVAsset *asset will point to that newly created AVURLAsset. Please don't set both assetURL and asset. + ** @discussion Setting the URL will override the current asset with a newly created AVURLAsset created from the given URL, and AVAsset *asset will point to that newly created AVURLAsset. Please don't set both assetURL and asset. ** @return Current URL the asset was initialized or nil if no URL was given. **/ @property (nullable, nonatomic, strong, readwrite) NSURL *assetURL; @@ -71,7 +69,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) int32_t periodicTimeObserverTimescale; //! Defaults to AVLayerVideoGravityResizeAspect -@property (nonatomic) NSString *gravity; +@property (nonatomic, copy) NSString *gravity; @property (nullable, nonatomic, weak, readwrite) id delegate; @@ -112,13 +110,13 @@ NS_ASSUME_NONNULL_BEGIN /** * @abstract Delegate method invoked when player playback time is updated. * @param videoNode The video node. - * @param second current playback time in seconds. + * @param timeInterval current playback time in seconds. */ - (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval; /** * @abstract Delegate method invoked when the video player stalls. * @param videoNode The video node that has experienced the stall - * @param second Current playback time when the stall happens + * @param timeInterval Current playback time when the stall happens */ - (void)videoNode:(ASVideoNode *)videoNode didStallAtTimeInterval:(NSTimeInterval)timeInterval; /** @@ -152,5 +150,3 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END -#endif - diff --git a/AsyncDisplayKit/ASVideoNode.mm b/Source/ASVideoNode.mm similarity index 83% rename from AsyncDisplayKit/ASVideoNode.mm rename to Source/ASVideoNode.mm index 9dc4d22077..7277fd0638 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/Source/ASVideoNode.mm @@ -7,14 +7,13 @@ // LICENSE file in the root directory of this source tree. An additional grant // of patent rights can be found in the PATENTS file in the same directory. // -#if TARGET_OS_IOS + #import -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASVideoNode.h" -#import "ASEqualityHelpers.h" -#import "ASInternalHelpers.h" -#import "ASDisplayNodeExtras.h" +#import +#import +#import +#import +#import static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { return ASObjectIsEqual(asset1, asset2) @@ -41,7 +40,6 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { @interface ASVideoNode () { - __weak id _delegate; struct { unsigned int delegateVideNodeShouldChangePlayerStateTo:1; unsigned int delegateVideoDidPlayToEnd:1; @@ -86,13 +84,15 @@ @interface ASVideoNode () @implementation ASVideoNode +@dynamic delegate; + // TODO: Support preview images with HTTP Live Streaming videos. #pragma mark - Construction and Layout -- (instancetype)init +- (instancetype)initWithCache:(id)cache downloader:(id)downloader { - if (!(self = [super init])) { + if (!(self = [super initWithCache:cache downloader:downloader])) { return nil; } @@ -118,6 +118,7 @@ - (ASDisplayNode *)constructPlayerNode - (AVPlayerItem *)constructPlayerItem { + ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(__instanceLock__); AVPlayerItem *playerItem = nil; @@ -135,6 +136,8 @@ - (AVPlayerItem *)constructPlayerItem - (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray *)requestedKeys { + ASDisplayNodeAssertMainThread(); + for (NSString *key in requestedKeys) { NSError *error = nil; AVKeyValueStatus keyStatus = [asset statusOfValueForKey:key error:&error]; @@ -158,7 +161,7 @@ - (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray *)requ } if (_delegateFlags.delegateVideoNodeDidSetCurrentItem) { - [_delegate videoNode:self didSetCurrentItem:playerItem]; + [self.delegate videoNode:self didSetCurrentItem:playerItem]; } if (self.image == nil && self.URL == nil) { @@ -235,7 +238,10 @@ - (void)layout - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - ASDN::MutexLocker l(__instanceLock__); + __instanceLock__.lock(); + ASDisplayNode *playerNode = _playerNode; + __instanceLock__.unlock(); + CGSize calculatedSize = constrainedSize; // Prevent crashes through if infinite width or height @@ -244,9 +250,9 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize calculatedSize = CGSizeZero; } - if (_playerNode) { - _playerNode.style.preferredSize = calculatedSize; - [_playerNode layoutThatFits:ASSizeRangeMake(CGSizeZero, calculatedSize)]; + if (playerNode != nil) { + playerNode.style.preferredSize = calculatedSize; + [playerNode layoutThatFits:ASSizeRangeMake(CGSizeZero, calculatedSize)]; } return calculatedSize; @@ -295,9 +301,12 @@ - (void)imageAtTime:(CMTime)imageTime completionHandler:(void(^)(UIImage *image) - (void)setVideoPlaceholderImage:(UIImage *)image { - ASDN::MutexLocker l(__instanceLock__); + __instanceLock__.lock(); + NSString *gravity = _gravity; + __instanceLock__.unlock(); + if (image != nil) { - self.contentMode = ASContentModeFromVideoGravity(_gravity); + self.contentMode = ASContentModeFromVideoGravity(gravity); } self.image = image; } @@ -311,6 +320,9 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { if (self.playerState != ASVideoNodePlayerStatePlaying) { self.playerState = ASVideoNodePlayerStateReadyToPlay; + if (_shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { + [self play]; + } } // If we don't yet have a placeholder image update it now that we should have data available for it if (self.image == nil && self.URL == nil) { @@ -329,7 +341,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N } if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || likelyToKeepUp) && ASInterfaceStateIncludesVisible(self.interfaceState)) { if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { - [_delegate videoNodeDidRecoverFromStall:self]; + [self.delegate videoNodeDidRecoverFromStall:self]; } [self play]; // autoresume after buffer catches up } @@ -354,7 +366,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N - (void)tapped { if (_delegateFlags.delegateDidTapVideoNode) { - [_delegate didTapVideoNode:self]; + [self.delegate didTapVideoNode:self]; } else { if (_shouldBePlaying) { @@ -365,9 +377,9 @@ - (void)tapped } } -- (void)fetchData +- (void)didEnterPreloadState { - [super fetchData]; + [super didEnterPreloadState]; ASDN::MutexLocker l(__instanceLock__); AVAsset *asset = self.asset; @@ -378,14 +390,14 @@ - (void)fetchData self.playerState = ASVideoNodePlayerStateLoading; if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) { - [_delegate videoNodeDidStartInitialLoading:self]; + [self.delegate videoNodeDidStartInitialLoading:self]; } NSArray *requestedKeys = @[@"playable"]; [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:^{ ASPerformBlockOnMainThread(^{ if (_delegateFlags.delegateVideoNodeDidFinishInitialLoading) { - [_delegate videoNodeDidFinishInitialLoading:self]; + [self.delegate videoNodeDidFinishInitialLoading:self]; } [self prepareToPlayAsset:asset withKeys:requestedKeys]; }); @@ -400,14 +412,14 @@ - (void)periodicTimeObserver:(CMTime)time } if (_delegateFlags.delegateVideoNodeDidPlayToTimeInterval) { - [_delegate videoNode:self didPlayToTimeInterval:timeInSeconds]; + [self.delegate videoNode:self didPlayToTimeInterval:timeInSeconds]; } } -- (void)clearFetchedData +- (void)didExitPreloadState { - [super clearFetchedData]; + [super didExitPreloadState]; { ASDN::MutexLocker l(__instanceLock__); @@ -422,12 +434,17 @@ - (void)didEnterVisibleState { [super didEnterVisibleState]; - ASDN::MutexLocker l(__instanceLock__); - + __instanceLock__.lock(); + BOOL shouldPlay = NO; if (_shouldBePlaying || _shouldAutoplay) { if (_player != nil && CMTIME_IS_VALID(_lastPlaybackTime)) { [_player seekToTime:_lastPlaybackTime]; } + shouldPlay = YES; + } + __instanceLock__.unlock(); + + if (shouldPlay) { [self play]; } } @@ -460,7 +477,7 @@ - (void)setPlayerState:(ASVideoNodePlayerState)playerState } if (_delegateFlags.delegateVideoNodeWillChangePlayerStateToState) { - [_delegate videoNode:self willChangePlayerState:oldState toState:playerState]; + [self.delegate videoNode:self willChangePlayerState:oldState toState:playerState]; } _playerState = playerState; @@ -468,10 +485,10 @@ - (void)setPlayerState:(ASVideoNodePlayerState)playerState - (void)setAssetURL:(NSURL *)assetURL { - ASDN::MutexLocker l(__instanceLock__); - + ASDisplayNodeAssertMainThread(); + if (ASObjectIsEqual(assetURL, self.assetURL) == NO) { - [self _setAndFetchAsset:[AVURLAsset assetWithURL:assetURL] url:assetURL]; + [self setAndFetchAsset:[AVURLAsset assetWithURL:assetURL] url:assetURL]; } } @@ -490,10 +507,10 @@ - (NSURL *)assetURL - (void)setAsset:(AVAsset *)asset { - ASDN::MutexLocker l(__instanceLock__); + ASDisplayNodeAssertMainThread(); - if (ASAssetIsEqual(asset, _asset) == NO) { - [self _setAndFetchAsset:asset url:nil]; + if (ASAssetIsEqual(asset, self.asset) == NO) { + [self setAndFetchAsset:asset url:nil]; } } @@ -503,12 +520,20 @@ - (AVAsset *)asset return _asset; } -- (void)_setAndFetchAsset:(AVAsset *)asset url:(NSURL *)assetURL +- (void)setAndFetchAsset:(AVAsset *)asset url:(NSURL *)assetURL { - [self clearFetchedData]; - _asset = asset; - _assetURL = assetURL; - [self setNeedsDataFetch]; + ASDisplayNodeAssertMainThread(); + + [self didExitPreloadState]; + + { + ASDN::MutexLocker l(__instanceLock__); + self.videoPlaceholderImage = nil; + _asset = asset; + _assetURL = assetURL; + } + + [self setNeedsPreload]; } - (void)setVideoComposition:(AVVideoComposition *)videoComposition @@ -551,28 +576,23 @@ - (AVPlayerLayer *)playerLayer return (AVPlayerLayer *)_playerNode.layer; } -- (id)delegate{ - return _delegate; -} - - (void)setDelegate:(id)delegate { [super setDelegate:delegate]; - _delegate = delegate; - if (_delegate == nil) { + if (delegate == nil) { memset(&_delegateFlags, 0, sizeof(_delegateFlags)); } else { - _delegateFlags.delegateVideNodeShouldChangePlayerStateTo = [_delegate respondsToSelector:@selector(videoNode:shouldChangePlayerStateTo:)]; - _delegateFlags.delegateVideoDidPlayToEnd = [_delegate respondsToSelector:@selector(videoDidPlayToEnd:)]; - _delegateFlags.delegateDidTapVideoNode = [_delegate respondsToSelector:@selector(didTapVideoNode:)]; - _delegateFlags.delegateVideoNodeWillChangePlayerStateToState = [_delegate respondsToSelector:@selector(videoNode:willChangePlayerState:toState:)]; - _delegateFlags.delegateVideoNodeDidPlayToTimeInterval = [_delegate respondsToSelector:@selector(videoNode:didPlayToTimeInterval:)]; - _delegateFlags.delegateVideoNodeDidStartInitialLoading = [_delegate respondsToSelector:@selector(videoNodeDidStartInitialLoading:)]; - _delegateFlags.delegateVideoNodeDidFinishInitialLoading = [_delegate respondsToSelector:@selector(videoNodeDidFinishInitialLoading:)]; - _delegateFlags.delegateVideoNodeDidSetCurrentItem = [_delegate respondsToSelector:@selector(videoNode:didSetCurrentItem:)]; - _delegateFlags.delegateVideoNodeDidStallAtTimeInterval = [_delegate respondsToSelector:@selector(videoNode:didStallAtTimeInterval:)]; - _delegateFlags.delegateVideoNodeDidRecoverFromStall = [_delegate respondsToSelector:@selector(videoNodeDidRecoverFromStall:)]; + _delegateFlags.delegateVideNodeShouldChangePlayerStateTo = [delegate respondsToSelector:@selector(videoNode:shouldChangePlayerStateTo:)]; + _delegateFlags.delegateVideoDidPlayToEnd = [delegate respondsToSelector:@selector(videoDidPlayToEnd:)]; + _delegateFlags.delegateDidTapVideoNode = [delegate respondsToSelector:@selector(didTapVideoNode:)]; + _delegateFlags.delegateVideoNodeWillChangePlayerStateToState = [delegate respondsToSelector:@selector(videoNode:willChangePlayerState:toState:)]; + _delegateFlags.delegateVideoNodeDidPlayToTimeInterval = [delegate respondsToSelector:@selector(videoNode:didPlayToTimeInterval:)]; + _delegateFlags.delegateVideoNodeDidStartInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidStartInitialLoading:)]; + _delegateFlags.delegateVideoNodeDidFinishInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidFinishInitialLoading:)]; + _delegateFlags.delegateVideoNodeDidSetCurrentItem = [delegate respondsToSelector:@selector(videoNode:didSetCurrentItem:)]; + _delegateFlags.delegateVideoNodeDidStallAtTimeInterval = [delegate respondsToSelector:@selector(videoNode:didStallAtTimeInterval:)]; + _delegateFlags.delegateVideoNodeDidRecoverFromStall = [delegate respondsToSelector:@selector(videoNodeDidRecoverFromStall:)]; } } @@ -610,21 +630,25 @@ - (void)setMuted:(BOOL)muted - (void)play { - ASDN::MutexLocker l(__instanceLock__); + __instanceLock__.lock(); if (![self isStateChangeValid:ASVideoNodePlayerStatePlaying]) { + __instanceLock__.unlock(); return; } if (_player == nil) { - [self setNeedsDataFetch]; + __instanceLock__.unlock(); + [self setNeedsPreload]; + __instanceLock__.lock(); } if (_playerNode == nil) { _playerNode = [self constructPlayerNode]; - [self addSubnode:_playerNode]; - + __instanceLock__.unlock(); + [self addSubnode:_playerNode]; + __instanceLock__.lock(); [self setNeedsLayout]; } @@ -632,6 +656,7 @@ - (void)play [_player play]; _shouldBePlaying = YES; + __instanceLock__.unlock(); } - (BOOL)ready @@ -659,7 +684,7 @@ - (BOOL)isPlaying - (BOOL)isStateChangeValid:(ASVideoNodePlayerState)state { if (_delegateFlags.delegateVideNodeShouldChangePlayerStateTo) { - if (![_delegate videoNode:self shouldChangePlayerStateTo:state]) { + if (![self.delegate videoNode:self shouldChangePlayerStateTo:state]) { return NO; } } @@ -686,7 +711,7 @@ - (void)didPlayToEnd:(NSNotification *)notification { self.playerState = ASVideoNodePlayerStateFinished; if (_delegateFlags.delegateVideoDidPlayToEnd) { - [_delegate videoDidPlayToEnd:self]; + [self.delegate videoDidPlayToEnd:self]; } if (_shouldAutorepeat) { @@ -701,7 +726,7 @@ - (void)videoNodeDidStall:(NSNotification *)notification { self.playerState = ASVideoNodePlayerStateLoading; if (_delegateFlags.delegateVideoNodeDidStallAtTimeInterval) { - [_delegate videoNode:self didStallAtTimeInterval:CMTimeGetSeconds(_player.currentItem.currentTime)]; + [self.delegate videoNode:self didStallAtTimeInterval:CMTimeGetSeconds(_player.currentItem.currentTime)]; } } @@ -751,9 +776,10 @@ - (ASDisplayNode *)playerNode - (void)setPlayerNode:(ASDisplayNode *)playerNode { - ASDN::MutexLocker l(__instanceLock__); + __instanceLock__.lock(); _playerNode = playerNode; - + __instanceLock__.unlock(); + [self setNeedsLayout]; } @@ -795,4 +821,3 @@ - (void)dealloc } @end -#endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/Source/ASVideoPlayerNode.h similarity index 70% rename from AsyncDisplayKit/ASVideoPlayerNode.h rename to Source/ASVideoPlayerNode.h index 2af9f4b9f0..8de81e6dc5 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/Source/ASVideoPlayerNode.h @@ -11,21 +11,23 @@ // #if TARGET_OS_IOS -#import -//#import -//#import -//#import +#import +#import +#import +#import @class AVAsset; +@class ASButtonNode; @protocol ASVideoPlayerNodeDelegate; -typedef enum { +typedef NS_ENUM(NSInteger, ASVideoPlayerNodeControlType) { ASVideoPlayerNodeControlTypePlaybackButton, ASVideoPlayerNodeControlTypeElapsedText, ASVideoPlayerNodeControlTypeDurationText, ASVideoPlayerNodeControlTypeScrubber, + ASVideoPlayerNodeControlTypeFullScreenButton, ASVideoPlayerNodeControlTypeFlexGrowSpacer, -} ASVideoPlayerNodeControlType; +}; NS_ASSUME_NONNULL_BEGIN @@ -37,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL controlsDisabled; -@property (nonatomic, assign, readonly) BOOL loadAssetWhenNodeBecomesVisible; +@property (nonatomic, assign, readonly) BOOL loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state. This flag does nothing."); #pragma mark - ASVideoNode property proxy /** @@ -51,17 +53,32 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign, readwrite) BOOL shouldAggressivelyRecoverFromStall; @property (nullable, nonatomic, strong, readwrite) NSURL *placeholderImageURL; +@property (nullable, nonatomic, strong, readwrite) AVAsset *asset; +/** + ** @abstract The URL with which the asset was initialized. + ** @discussion Setting the URL will override the current asset with a newly created AVURLAsset created from the given URL, and AVAsset *asset will point to that newly created AVURLAsset. Please don't set both assetURL and asset. + ** @return Current URL the asset was initialized or nil if no URL was given. + **/ +@property (nullable, nonatomic, strong, readwrite) NSURL *assetURL; + +/// You should never set any value on the backing video node. Use exclusivively the video player node to set properties +@property (nonatomic, strong, readonly) ASVideoNode *videoNode; + //! Defaults to 100 @property (nonatomic, assign) int32_t periodicTimeObserverTimescale; //! Defaults to AVLayerVideoGravityResizeAspect -@property (nonatomic) NSString *gravity; +@property (nonatomic, copy) NSString *gravity; -- (instancetype)initWithUrl:(NSURL*)url; -- (instancetype)initWithAsset:(AVAsset*)asset; +#pragma mark - Lifecycle +- (instancetype)initWithURL:(NSURL *)URL; +- (instancetype)initWithAsset:(AVAsset *)asset; - (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix; -- (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible; -- (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible; -- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible; + +#pragma mark Lifecycle Deprecated +- (instancetype)initWithUrl:(NSURL *)url ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore."); +- (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore."); +- (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore."); +- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore."); #pragma mark - Public API - (void)seekToTime:(CGFloat)percentComplete; @@ -77,14 +94,14 @@ NS_ASSUME_NONNULL_BEGIN @optional /** * @abstract Delegate method invoked before creating controlbar controls - * @param videoPlayer + * @param videoPlayer The sender */ - (NSArray *)videoPlayerNodeNeededDefaultControls:(ASVideoPlayerNode*)videoPlayer; /** * @abstract Delegate method invoked before creating default controls, asks delegate for custom controls dictionary. * This dictionary must constain only ASDisplayNode subclass objects. - * @param videoPlayer + * @param videoPlayer The sender * @discussion - This method is invoked only when developer implements videoPlayerNodeLayoutSpec:forControls:forMaximumSize: * and gives ability to add custom constrols to ASVideoPlayerNode, for example mute button. */ @@ -92,7 +109,7 @@ NS_ASSUME_NONNULL_BEGIN /** * @abstract Delegate method invoked in layoutSpecThatFits: - * @param videoPlayer + * @param videoPlayer The sender * @param controls - Dictionary of controls which are used in videoPlayer; Dictionary keys are ASVideoPlayerNodeControlType * @param maxSize - Maximum size for ASVideoPlayerNode * @discussion - Developer can layout whole ASVideoPlayerNode as he wants. ASVideoNode is locked and it can't be changed @@ -104,10 +121,10 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark Text delegate methods /** * @abstract Delegate method invoked before creating ASVideoPlayerNodeControlTypeElapsedText and ASVideoPlayerNodeControlTypeDurationText - * @param videoPlayer - * @param timeLabelType + * @param videoPlayer The sender + * @param timeLabelType The of the time label */ -- (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayerNode timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType; +- (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayer timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType; - (NSString *)videoPlayerNode:(ASVideoPlayerNode *)videoPlayerNode timeStringForTimeLabelType:(ASVideoPlayerNodeControlType)timeLabelType forTime:(CMTime)time; @@ -125,32 +142,43 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Playback button delegate methods - (UIColor *)videoPlayerNodePlaybackButtonTint:(ASVideoPlayerNode *)videoPlayer; +#pragma mark - Fullscreen button delegate methods + +- (UIImage *)videoPlayerNodeFullScreenButtonImage:(ASVideoPlayerNode *)videoPlayer; + #pragma mark ASVideoNodeDelegate proxy methods /** * @abstract Delegate method invoked when ASVideoPlayerNode is taped. - * @param videoPlayerNode The ASVideoPlayerNode that was tapped. + * @param videoPlayer The ASVideoPlayerNode that was tapped. */ - (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer; + +/** + * @abstract Delegate method invoked when fullcreen button is taped. + * @param buttonNode The fullscreen button node that was tapped. + */ +- (void)didTapFullScreenButtonNode:(ASButtonNode *)buttonNode; + /** * @abstract Delegate method invoked when ASVideoNode playback time is updated. - * @param videoPlayerNode The video player node - * @param second current playback time. + * @param videoPlayer The video player node + * @param time current playback time. */ - (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didPlayToTime:(CMTime)time; /** * @abstract Delegate method invoked when ASVideoNode changes state. - * @param videoPlayerNode The ASVideoPlayerNode whose ASVideoNode is changing state. + * @param videoPlayer The ASVideoPlayerNode whose ASVideoNode is changing state. * @param state ASVideoNode state before this change. - * @param toSate ASVideoNode new state. + * @param toState ASVideoNode new state. * @discussion This method is called after each state change */ - (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer willChangeVideoNodeState:(ASVideoNodePlayerState)state toVideoNodeState:(ASVideoNodePlayerState)toState; /** * @abstract Delegate method is invoked when ASVideoNode decides to change state. - * @param videoPlayerNode The ASVideoPlayerNode whose ASVideoNode is changing state. + * @param videoPlayer The ASVideoPlayerNode whose ASVideoNode is changing state. * @param state ASVideoNode that is going to be set. * @discussion Delegate method invoked when player changes it's state to * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused @@ -174,7 +202,7 @@ NS_ASSUME_NONNULL_BEGIN /** * @abstract Delegate method invoked when the ASVideoNode stalls. * @param videoPlayer The video player node that has experienced the stall - * @param second Current playback time when the stall happens + * @param timeInterval Current playback time when the stall happens */ - (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didStallAtTimeInterval:(NSTimeInterval)timeInterval; diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/Source/ASVideoPlayerNode.mm similarity index 79% rename from AsyncDisplayKit/ASVideoPlayerNode.mm rename to Source/ASVideoPlayerNode.mm index 5c26ddec3e..f9eb4b5391 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/Source/ASVideoPlayerNode.mm @@ -10,13 +10,21 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASVideoPlayerNode.h" -#import "ASDefaultPlaybackButton.h" -#import "ASDisplayNodeInternal.h" +#import + +#if TARGET_OS_IOS + +#import + +#import + +#import +#import +#import static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; -@interface ASVideoPlayerNode() +@interface ASVideoPlayerNode() { __weak id _delegate; @@ -26,6 +34,7 @@ @interface ASVideoPlayerNode() unsigned int delegateSpinnerTintColor:1; unsigned int delegateSpinnerStyle:1; unsigned int delegatePlaybackButtonTint:1; + unsigned int delegateFullScreenButtonImage:1; unsigned int delegateScrubberMaximumTrackTintColor:1; unsigned int delegateScrubberMinimumTrackTintColor:1; unsigned int delegateScrubberThumbTintColor:1; @@ -38,6 +47,7 @@ @interface ASVideoPlayerNode() unsigned int delegateVideoNodeShouldChangeState:1; unsigned int delegateVideoNodePlaybackDidFinish:1; unsigned int delegateDidTapVideoPlayerNode:1; + unsigned int delegateDidTapFullScreenButtonNode:1; unsigned int delegateVideoPlayerNodeDidSetCurrentItem:1; unsigned int delegateVideoPlayerNodeDidStallAtTimeInterval:1; unsigned int delegateVideoPlayerNodeDidStartInitialLoading:1; @@ -45,11 +55,13 @@ @interface ASVideoPlayerNode() unsigned int delegateVideoPlayerNodeDidRecoverFromStall:1; } _delegateFlags; - NSURL *_url; - AVAsset *_asset; - AVVideoComposition *_videoComposition; - AVAudioMix *_audioMix; + // The asset passed in the initializer will be assigned as pending asset. As soon as the first + // preload state happened all further asset handling is made by using the asset of the backing + // video node + AVAsset *_pendingAsset; + // The backing video node. Ideally this is the source of truth and the video player node should + // not handle anything related to asset management ASVideoNode *_videoNode; NSArray *_neededDefaultControls; @@ -57,13 +69,13 @@ @interface ASVideoPlayerNode() NSMutableDictionary *_cachedControls; ASDefaultPlaybackButton *_playbackButtonNode; + ASButtonNode *_fullScreenButtonNode; ASTextNode *_elapsedTextNode; ASTextNode *_durationTextNode; ASDisplayNode *_scrubberNode; ASStackLayoutSpec *_controlFlexGrowSpacerSpec; ASDisplayNode *_spinnerNode; - BOOL _loadAssetWhenNodeBecomesVisible; BOOL _isSeeking; CMTime _duration; @@ -86,122 +98,133 @@ @implementation ASVideoPlayerNode @dynamic placeholderImageURL; +#pragma mark - Lifecycle + - (instancetype)init { if (!(self = [super init])) { return nil; } - [self _init]; + [self _initControlsAndVideoNode]; return self; } -- (instancetype)initWithUrl:(NSURL*)url +- (instancetype)initWithAsset:(AVAsset *)asset { - if (!(self = [super init])) { + if (!(self = [self init])) { return nil; } - _url = url; - _asset = [AVAsset assetWithURL:_url]; - _loadAssetWhenNodeBecomesVisible = YES; - - [self _init]; + _pendingAsset = asset; return self; } -- (instancetype)initWithAsset:(AVAsset *)asset +- (instancetype)initWithURL:(NSURL *)URL { - if (!(self = [super init])) { - return nil; - } - - _asset = asset; - _loadAssetWhenNodeBecomesVisible = YES; - - [self _init]; - - return self; + return [self initWithAsset:[AVAsset assetWithURL:URL]]; } --(instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix +- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix { - if (!(self = [super init])) { + if (!(self = [self initWithAsset:asset])) { return nil; } - - _asset = asset; - _videoComposition = videoComposition; - _audioMix = audioMix; - _loadAssetWhenNodeBecomesVisible = YES; - - [self _init]; + + _videoNode.videoComposition = videoComposition; + _videoNode.audioMix = audioMix; return self; } -- (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible +- (void)_initControlsAndVideoNode { - if (!(self = [super init])) { - return nil; - } + _defaultControlsColor = [UIColor whiteColor]; + _cachedControls = [[NSMutableDictionary alloc] init]; + + _videoNode = [[ASVideoNode alloc] init]; + _videoNode.delegate = self; + [self addSubnode:_videoNode]; +} - _url = url; - _asset = [AVAsset assetWithURL:_url]; - _loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible; +#pragma mark Deprecated - [self _init]; +- (instancetype)initWithUrl:(NSURL *)url +{ + return [self initWithURL:url]; +} - return self; +- (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible +{ + return [self initWithURL:url]; } - (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible { - if (!(self = [super init])) { - return nil; - } + return [self initWithAsset:asset]; +} - _asset = asset; - _loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible; +- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible +{ + return [self initWithAsset:asset videoComposition:videoComposition audioMix:audioMix]; +} - [self _init]; +#pragma mark - Setter / Getter - return self; +- (void)setAssetURL:(NSURL *)assetURL +{ + ASDisplayNodeAssertMainThread(); + + self.asset = [AVAsset assetWithURL:assetURL]; } --(instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible +- (NSURL *)assetURL { - if (!(self = [super init])) { - return nil; + NSURL *url = nil; + { + ASDN::MutexLocker l(__instanceLock__); + if ([_pendingAsset isKindOfClass:AVURLAsset.class]) { + url = ((AVURLAsset *)_pendingAsset).URL; + } } + + return url ?: _videoNode.assetURL; +} - _asset = asset; - _videoComposition = videoComposition; - _audioMix = audioMix; - _loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible; - - [self _init]; +- (void)setAsset:(AVAsset *)asset +{ + ASDisplayNodeAssertMainThread(); - return self; + __instanceLock__.lock(); + + // Clean out pending asset + _pendingAsset = nil; + + // Set asset based on interface state + if ((ASInterfaceStateIncludesPreload(self.interfaceState))) { + // Don't hold the lock while accessing the subnode + __instanceLock__.unlock(); + _videoNode.asset = asset; + return; + } + + _pendingAsset = asset; + __instanceLock__.unlock(); } -- (void)_init +- (AVAsset *)asset { - _defaultControlsColor = [UIColor whiteColor]; - _cachedControls = [[NSMutableDictionary alloc] init]; + __instanceLock__.lock(); + AVAsset *asset = _pendingAsset; + __instanceLock__.unlock(); - _videoNode = [[ASVideoNode alloc] init]; - _videoNode.delegate = self; - if (_loadAssetWhenNodeBecomesVisible == NO) { - _videoNode.asset = _asset; - _videoNode.videoComposition = _videoComposition; - _videoNode.audioMix = _audioMix; - } - [self addSubnode:_videoNode]; + return asset ?: _videoNode.asset; } +#pragma mark - ASDisplayNode + - (void)didLoad { [super didLoad]; @@ -211,96 +234,99 @@ - (void)didLoad } } -- (void)didEnterVisibleState +- (void)didEnterPreloadState { - [super didEnterVisibleState]; - - ASDN::MutexLocker l(__instanceLock__); + [super didEnterPreloadState]; - if (_loadAssetWhenNodeBecomesVisible) { - if (_asset != _videoNode.asset) { - _videoNode.asset = _asset; - } - if (_videoComposition != _videoNode.videoComposition) { - _videoNode.videoComposition = _videoComposition; - } - if (_audioMix != _videoNode.audioMix) { - _videoNode.audioMix = _audioMix; - } + AVAsset *pendingAsset = nil; + { + ASDN::MutexLocker l(__instanceLock__); + pendingAsset = _pendingAsset; + _pendingAsset = nil; } -} -- (NSArray *)createDefaultControlElementArray -{ - if (_delegateFlags.delegateNeededDefaultControls) { - return [_delegate videoPlayerNodeNeededDefaultControls:self]; + // If we enter preload state we apply the pending asset to load to the video node so it can start and fetch the asset + if (pendingAsset != nil && _videoNode.asset != pendingAsset) { + _videoNode.asset = pendingAsset; } - - return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), - @(ASVideoPlayerNodeControlTypeElapsedText), - @(ASVideoPlayerNodeControlTypeScrubber), - @(ASVideoPlayerNodeControlTypeDurationText) ]; } #pragma mark - UI + - (void)createControls { - ASDN::MutexLocker l(__instanceLock__); + { + ASDN::MutexLocker l(__instanceLock__); - if (_controlsDisabled) { - return; - } + if (_controlsDisabled) { + return; + } - if (_neededDefaultControls == nil) { - _neededDefaultControls = [self createDefaultControlElementArray]; - } - - if (_cachedControls == nil) { - _cachedControls = [[NSMutableDictionary alloc] init]; - } - - for (id object in _neededDefaultControls) { - ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[object integerValue]; - switch (type) { - case ASVideoPlayerNodeControlTypePlaybackButton: - [self createPlaybackButton]; - break; - case ASVideoPlayerNodeControlTypeElapsedText: - [self createElapsedTextField]; - break; - case ASVideoPlayerNodeControlTypeDurationText: - [self createDurationTextField]; - break; - case ASVideoPlayerNodeControlTypeScrubber: - [self createScrubber]; - break; - case ASVideoPlayerNodeControlTypeFlexGrowSpacer: - [self createControlFlexGrowSpacer]; - break; - default: - break; + if (_neededDefaultControls == nil) { + _neededDefaultControls = [self createDefaultControlElementArray]; } - } - if (_delegateFlags.delegateCustomControls && _delegateFlags.delegateLayoutSpecForControls) { - NSDictionary *customControls = [_delegate videoPlayerNodeCustomControls:self]; - for (id key in customControls) { - id node = customControls[key]; - if (![node isKindOfClass:[ASDisplayNode class]]) { - continue; + if (_cachedControls == nil) { + _cachedControls = [[NSMutableDictionary alloc] init]; + } + + for (id object in _neededDefaultControls) { + ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[object integerValue]; + switch (type) { + case ASVideoPlayerNodeControlTypePlaybackButton: + [self _locked_createPlaybackButton]; + break; + case ASVideoPlayerNodeControlTypeElapsedText: + [self _locked_createElapsedTextField]; + break; + case ASVideoPlayerNodeControlTypeDurationText: + [self _locked_createDurationTextField]; + break; + case ASVideoPlayerNodeControlTypeScrubber: + [self _locked_createScrubber]; + break; + case ASVideoPlayerNodeControlTypeFullScreenButton: + [self _locked_createFullScreenButton]; + break; + case ASVideoPlayerNodeControlTypeFlexGrowSpacer: + [self _locked_createControlFlexGrowSpacer]; + break; + default: + break; } + } + + if (_delegateFlags.delegateCustomControls && _delegateFlags.delegateLayoutSpecForControls) { + NSDictionary *customControls = [_delegate videoPlayerNodeCustomControls:self]; + for (id key in customControls) { + id node = customControls[key]; + if (![node isKindOfClass:[ASDisplayNode class]]) { + continue; + } - [self addSubnode:node]; - [_cachedControls setObject:node forKey:key]; + [self addSubnode:node]; + [_cachedControls setObject:node forKey:key]; + } } } ASPerformBlockOnMainThread(^{ - ASDN::MutexLocker l(__instanceLock__); [self setNeedsLayout]; }); } +- (NSArray *)createDefaultControlElementArray +{ + if (_delegateFlags.delegateNeededDefaultControls) { + return [_delegate videoPlayerNodeNeededDefaultControls:self]; + } + + return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), + @(ASVideoPlayerNodeControlTypeElapsedText), + @(ASVideoPlayerNodeControlTypeScrubber), + @(ASVideoPlayerNodeControlTypeDurationText) ]; +} + - (void)removeControls { for (ASDisplayNode *node in [_cachedControls objectEnumerator]) { @@ -315,12 +341,13 @@ - (void)cleanCachedControls [_cachedControls removeAllObjects]; _playbackButtonNode = nil; + _fullScreenButtonNode = nil; _elapsedTextNode = nil; _durationTextNode = nil; _scrubberNode = nil; } -- (void)createPlaybackButton +- (void)_locked_createPlaybackButton { if (_playbackButtonNode == nil) { _playbackButtonNode = [[ASDefaultPlaybackButton alloc] init]; @@ -343,7 +370,24 @@ - (void)createPlaybackButton [self addSubnode:_playbackButtonNode]; } -- (void)createElapsedTextField +- (void)_locked_createFullScreenButton +{ + if (_fullScreenButtonNode == nil) { + _fullScreenButtonNode = [[ASButtonNode alloc] init]; + _fullScreenButtonNode.style.preferredSize = CGSizeMake(16.0, 22.0); + + if (_delegateFlags.delegateFullScreenButtonImage) { + [_fullScreenButtonNode setImage:[_delegate videoPlayerNodeFullScreenButtonImage:self] forState:UIControlStateNormal]; + } + + [_fullScreenButtonNode addTarget:self action:@selector(didTapFullScreenButton:) forControlEvents:ASControlNodeEventTouchUpInside]; + [_cachedControls setObject:_fullScreenButtonNode forKey:@(ASVideoPlayerNodeControlTypeFullScreenButton)]; + } + + [self addSubnode:_fullScreenButtonNode]; +} + +- (void)_locked_createElapsedTextField { if (_elapsedTextNode == nil) { _elapsedTextNode = [[ASTextNode alloc] init]; @@ -356,7 +400,7 @@ - (void)createElapsedTextField [self addSubnode:_elapsedTextNode]; } -- (void)createDurationTextField +- (void)_locked_createDurationTextField { if (_durationTextNode == nil) { _durationTextNode = [[ASTextNode alloc] init]; @@ -366,10 +410,11 @@ - (void)createDurationTextField [_cachedControls setObject:_durationTextNode forKey:@(ASVideoPlayerNodeControlTypeDurationText)]; } + [self updateDurationTimeLabel]; [self addSubnode:_durationTextNode]; } -- (void)createScrubber +- (void)_locked_createScrubber { if (_scrubberNode == nil) { __weak __typeof__(self) weakSelf = self; @@ -413,7 +458,7 @@ - (void)createScrubber [self addSubnode:_scrubberNode]; } -- (void)createControlFlexGrowSpacer +- (void)_locked_createControlFlexGrowSpacer { if (_controlFlexGrowSpacerSpec == nil) { _controlFlexGrowSpacerSpec = [[ASStackLayoutSpec alloc] init]; @@ -624,6 +669,11 @@ - (void)didTapPlaybackButton:(ASControlNode*)node [self togglePlayPause]; } +- (void)didTapFullScreenButton:(ASButtonNode*)node +{ + [_delegate didTapFullScreenButtonNode:node]; +} + - (void)beginSeek { _isSeeking = YES; @@ -692,6 +742,10 @@ - (NSArray *)controlsForLayoutSpec if (_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]) { [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]]; } + + if (_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]) { + [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]]; + } return controls; } @@ -795,7 +849,9 @@ - (void)setDelegate:(id)delegate _delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)]; _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)]; _delegateFlags.delegatePlaybackButtonTint = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackButtonTint:)]; + _delegateFlags.delegateFullScreenButtonImage = [_delegate respondsToSelector:@selector(videoPlayerNodeFullScreenButtonImage:)]; _delegateFlags.delegateDidTapVideoPlayerNode = [_delegate respondsToSelector:@selector(didTapVideoPlayerNode:)]; + _delegateFlags.delegateDidTapFullScreenButtonNode = [_delegate respondsToSelector:@selector(didTapFullScreenButtonNode:)]; _delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem = [_delegate respondsToSelector:@selector(videoPlayerNode:didSetCurrentItem:)]; _delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval = [_delegate respondsToSelector:@selector(videoPlayerNode:didStallAtTimeInterval:)]; _delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidStartInitialLoading:)]; @@ -892,6 +948,11 @@ - (NSURL*) placeholderImageURL return _videoNode.URL; } +- (ASVideoNode*)videoNode +{ + return _videoNode; +} + - (void)setShouldAggressivelyRecoverFromStall:(BOOL)shouldAggressivelyRecoverFromStall { if (_shouldAggressivelyRecoverFromStall == shouldAggressivelyRecoverFromStall) { @@ -904,6 +965,9 @@ - (void)setShouldAggressivelyRecoverFromStall:(BOOL)shouldAggressivelyRecoverFro #pragma mark - Helpers - (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNodeControlType)type { + if (!CMTIME_IS_VALID(time)) { + return @"00:00"; + } if (_delegateFlags.delegateTimeLabelAttributedString) { return [_delegate videoPlayerNode:self timeStringForTimeLabelType:type forTime:time]; } @@ -924,3 +988,5 @@ - (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNo } @end + +#endif // TARGET_OS_IOS diff --git a/AsyncDisplayKit/ASViewController.h b/Source/ASViewController.h similarity index 80% rename from AsyncDisplayKit/ASViewController.h rename to Source/ASViewController.h index 28d77399c0..3b4ce458c3 100644 --- a/AsyncDisplayKit/ASViewController.h +++ b/Source/ASViewController.h @@ -21,25 +21,34 @@ NS_ASSUME_NONNULL_BEGIN typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitCollectionBlock)(UITraitCollection *traitCollection); typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(CGSize windowSize); +/** + * ASViewController allows you to have a completely node backed hierarchy. It automatically + * handles @c ASVisibilityDepth, automatic range mode and propogating @c ASDisplayTraits to contained nodes. + * + * You can opt-out of node backed hierarchy and use it like a normal UIViewController. + * More importantly, you can use it as a base class for all of your view controllers among which some use a node hierarchy and some don't. + * See examples/ASDKgram project for actual implementation. + */ @interface ASViewController<__covariant DisplayNodeType : ASDisplayNode *> : UIViewController /** - * ASViewController Designated initializer. - * - * @discussion ASViewController allows you to have a completely node backed heirarchy. It automatically - * handles @c ASVisibilityDepth, automatic range mode and propogating @c ASDisplayTraits to contained nodes. + * ASViewController initializer. * * @param node An ASDisplayNode which will provide the root view (self.view) * @return An ASViewController instance whose root view will be backed by the provided ASDisplayNode. * * @see ASVisibilityDepth */ -- (instancetype)initWithNode:(DisplayNodeType)node NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithNode:(DisplayNodeType)node; + +NS_ASSUME_NONNULL_END /** * @return node Returns the ASDisplayNode which provides the backing view to the view controller. */ -@property (nonatomic, strong, readonly) DisplayNodeType node; +@property (nonatomic, strong, readonly, null_unspecified) DisplayNodeType node; + +NS_ASSUME_NONNULL_BEGIN /** * Set this block to customize the ASDisplayTraits returned when the VC transitions to the given traitCollection. @@ -64,16 +73,6 @@ typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(C // Refer to examples/SynchronousConcurrency, AsyncViewController.m @property (nonatomic, assign) BOOL neverShowPlaceholders; - -/** - * The constrained size used to measure the backing node. - * - * @discussion Defaults to providing a size range that uses the view controller view's bounds as - * both the min and max definitions. Override this method to provide a custom size range to the - * backing node. - */ -- (ASSizeRange)nodeConstrainedSize AS_WARN_UNUSED_RESULT; - @end @interface ASViewController (ASRangeControllerUpdateRangeProtocol) @@ -88,11 +87,16 @@ typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(C @end -@interface ASViewController (Unavailable) - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil AS_UNAVAILABLE("ASViewController requires using -initWithNode:"); +@interface ASViewController (Deprecated) -- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder AS_UNAVAILABLE("ASViewController requires using -initWithNode:"); +/** + * The constrained size used to measure the backing node. + * + * @discussion Defaults to providing a size range that uses the view controller view's bounds as + * both the min and max definitions. Override this method to provide a custom size range to the + * backing node. + */ +- (ASSizeRange)nodeConstrainedSize AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Set the size directly to the view's frame"); @end diff --git a/AsyncDisplayKit/ASViewController.mm b/Source/ASViewController.mm similarity index 55% rename from AsyncDisplayKit/ASViewController.mm rename to Source/ASViewController.mm index de8b5dc3ac..c3c1ad7307 100644 --- a/AsyncDisplayKit/ASViewController.mm +++ b/Source/ASViewController.mm @@ -10,16 +10,13 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASViewController.h" -#import "ASAssert.h" -#import "ASAvailability.h" -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASLayout.h" -#import "ASTraitCollection.h" -#import "ASEnvironmentInternal.h" -#import "ASRangeControllerUpdateRangeProtocol+Beta.h" -#import "ASInternalHelpers.h" +#import +#import +#import +#import +#import +#import +#import #define AS_LOG_VISIBILITY_CHANGES 0 @@ -35,14 +32,24 @@ @implementation ASViewController - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - ASDisplayNodeAssert(NO, @"ASViewController requires using -initWithNode:"); - return [self initWithNode:[[ASDisplayNode alloc] init]]; + if (!(self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { + return nil; + } + + [self _initializeInstance]; + + return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { - ASDisplayNodeAssert(NO, @"ASViewController requires using -initWithNode:"); - return [self initWithNode:[[ASDisplayNode alloc] init]]; + if (!(self = [super initWithCoder:aDecoder])) { + return nil; + } + + [self _initializeInstance]; + + return self; } - (instancetype)initWithNode:(ASDisplayNode *)node @@ -51,15 +58,36 @@ - (instancetype)initWithNode:(ASDisplayNode *)node return nil; } - ASDisplayNodeAssertNotNil(node, @"Node must not be nil"); - ASDisplayNodeAssertTrue(!node.layerBacked); _node = node; + [self _initializeInstance]; + return self; +} + +- (void)_initializeInstance +{ + if (_node == nil) { + return; + } + _selfConformsToRangeModeProtocol = [self conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; _nodeConformsToRangeModeProtocol = [_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; _automaticallyAdjustRangeModeBasedOnViewEvents = _selfConformsToRangeModeProtocol || _nodeConformsToRangeModeProtocol; - - return self; + + // In case the node will get loaded + if (_node.nodeLoaded) { + // Node already loaded the view + [self view]; + } else { + // If the node didn't load yet add ourselves as on did load observer to load the view in case the node gets loaded + // before the view controller + __weak __typeof__(self) weakSelf = self; + [_node onDidLoad:^(__kindof ASDisplayNode * _Nonnull node) { + if ([weakSelf isViewLoaded] == NO) { + [weakSelf view]; + } + }]; + } } - (void)dealloc @@ -69,13 +97,18 @@ - (void)dealloc - (void)loadView { - ASDisplayNodeAssertTrue(!_node.layerBacked); - // Apple applies a frame and autoresizing masks we need. Allocating a view is not // nearly as expensive as adding and removing it from a hierarchy, and fortunately // we can avoid that here. Enabling layerBacking on a single node in the hierarchy // will have a greater performance benefit than the impact of this transient view. [super loadView]; + + if (_node == nil) { + return; + } + + ASDisplayNodeAssertTrue(!_node.layerBacked); + UIView *view = self.view; CGRect frame = view.frame; UIViewAutoresizing autoresizingMask = view.autoresizingMask; @@ -88,14 +121,8 @@ - (void)loadView // ensure that self.node has a valid trait collection before a subclass's implementation of viewDidLoad. // Any subnodes added in viewDidLoad will then inherit the proper environment. - if (AS_AT_LEAST_IOS8) { - ASEnvironmentTraitCollection traitCollection = [self environmentTraitCollectionForUITraitCollection:self.traitCollection]; - [self progagateNewEnvironmentTraitCollection:traitCollection]; - } else { - ASEnvironmentTraitCollection traitCollection = ASEnvironmentTraitCollectionMakeDefault(); - traitCollection.containerSize = self.view.bounds.size; - [self progagateNewEnvironmentTraitCollection:traitCollection]; - } + ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; + [self propagateNewTraitCollection:traitCollection]; } - (void)viewWillLayoutSubviews @@ -104,24 +131,23 @@ - (void)viewWillLayoutSubviews // Before layout, make sure that our trait collection containerSize actually matches the size of our bounds. // If not, we need to update the traits and propagate them. - if (CGSizeEqualToSize(self.node.environmentTraitCollection.containerSize, self.view.bounds.size) == NO) { + + CGSize boundsSize = self.view.bounds.size; + if (CGSizeEqualToSize(self.node.primitiveTraitCollection.containerSize, boundsSize) == NO) { [UIView performWithoutAnimation:^{ - ASEnvironmentTraitCollection environmentTraitCollection; - if (AS_AT_LEAST_IOS8) { - environmentTraitCollection = [self environmentTraitCollectionForUITraitCollection:self.traitCollection]; - } else { - environmentTraitCollection = ASEnvironmentTraitCollectionMakeDefault(); - } - environmentTraitCollection.containerSize = self.view.bounds.size; + ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; + traitCollection.containerSize = boundsSize; + // this method will call measure - [self progagateNewEnvironmentTraitCollection:environmentTraitCollection]; + [self propagateNewTraitCollection:traitCollection]; }]; } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + // Call layoutThatFits: to let the node prepare for a layout that will happen shortly in the layout pass of the view. + // If the node's constrained size didn't change between the last layout pass it's a no-op [_node layoutThatFits:[self nodeConstrainedSize]]; - } - - if (!AS_AT_LEAST_IOS9) { - [self _legacyHandleViewDidLayoutSubviews]; +#pragma clang diagnostic pop } } @@ -129,7 +155,7 @@ - (void)viewDidLayoutSubviews { if (_ensureDisplayed && self.neverShowPlaceholders) { _ensureDisplayed = NO; - [self.node recursivelyEnsureDisplaySynchronously:YES]; + [_node recursivelyEnsureDisplaySynchronously:YES]; } [super viewDidLayoutSubviews]; } @@ -141,13 +167,10 @@ - (void)viewWillAppear:(BOOL)animated [super viewWillAppear:animated]; _ensureDisplayed = YES; - // A measure as well as layout pass is forced this early to get nodes like ASCollectionNode, ASTableNode etc. + // A layout pass is forced this early to get nodes like ASCollectionNode, ASTableNode etc. // into the hierarchy before UIKit applies the scroll view inset adjustments, if automatic subnode management // is enabled. Otherwise the insets would not be applied. - [_node layoutThatFits:[self nodeConstrainedSize]]; [_node.view layoutIfNeeded]; - - [_node recursivelyFetchData]; if (_parentManagesVisibilityDepth == NO) { [self setVisibilityDepth:0]; @@ -209,7 +232,9 @@ - (void)setAutomaticallyAdjustRangeModeBasedOnViewEvents:(BOOL)automaticallyAdju - (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode { - if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { return; } + if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { + return; + } if (_selfConformsToRangeModeProtocol) { id rangeUpdater = (id)self; @@ -226,12 +251,7 @@ - (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode - (ASSizeRange)nodeConstrainedSize { - if (AS_AT_LEAST_IOS9) { - CGSize viewSize = self.view.bounds.size; - return ASSizeRangeMake(viewSize); - } else { - return [self _legacyConstrainedSize]; - } + return ASSizeRangeMake(self.view.bounds.size); } - (ASInterfaceState)interfaceState @@ -239,83 +259,39 @@ - (ASInterfaceState)interfaceState return _node.interfaceState; } -#pragma mark - Legacy Layout Handling - -- (BOOL)_shouldLayoutTheLegacyWay -{ - BOOL isModalViewController = (self.presentingViewController != nil && self.presentedViewController == nil); - BOOL hasNavigationController = (self.navigationController != nil); - BOOL hasParentViewController = (self.parentViewController != nil); - if (isModalViewController && !hasNavigationController && !hasParentViewController) { - return YES; - } - - // Check if the view controller is a root view controller - BOOL isRootViewController = self.view.window.rootViewController == self; - if (isRootViewController) { - return YES; - } - - return NO; -} - -- (ASSizeRange)_legacyConstrainedSize -{ - // In modal presentation the view does not have the right bounds in iOS7 and iOS8. As workaround using the superviews - // view bounds - UIView *view = self.view; - CGSize viewSize = view.bounds.size; - if ([self _shouldLayoutTheLegacyWay]) { - UIView *superview = view.superview; - if (superview != nil) { - viewSize = superview.bounds.size; - } - } - return ASSizeRangeMake(viewSize, viewSize); -} +#pragma mark - ASTraitEnvironment -- (void)_legacyHandleViewDidLayoutSubviews -{ - // In modal presentation or as root viw controller the view does not automatic resize in iOS7 and iOS8. - // As workaround we adjust the frame of the view manually - if ([self _shouldLayoutTheLegacyWay]) { - CGSize maxConstrainedSize = [self nodeConstrainedSize].max; - _node.frame = (CGRect){.origin = CGPointZero, .size = maxConstrainedSize}; - } -} - -#pragma mark - ASEnvironmentTraitCollection - -- (ASEnvironmentTraitCollection)environmentTraitCollectionForUITraitCollection:(UITraitCollection *)traitCollection +- (ASPrimitiveTraitCollection)primitiveTraitCollectionForUITraitCollection:(UITraitCollection *)traitCollection { if (self.overrideDisplayTraitsWithTraitCollection) { ASTraitCollection *asyncTraitCollection = self.overrideDisplayTraitsWithTraitCollection(traitCollection); - return [asyncTraitCollection environmentTraitCollection]; + return [asyncTraitCollection primitiveTraitCollection]; } ASDisplayNodeAssertMainThread(); - ASEnvironmentTraitCollection asyncTraitCollection = ASEnvironmentTraitCollectionFromUITraitCollection(traitCollection); + ASPrimitiveTraitCollection asyncTraitCollection = ASPrimitiveTraitCollectionFromUITraitCollection(traitCollection); asyncTraitCollection.containerSize = self.view.frame.size; return asyncTraitCollection; } -- (void)progagateNewEnvironmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection +- (void)propagateNewTraitCollection:(ASPrimitiveTraitCollection)traitCollection { - ASEnvironmentState environmentState = self.node.environmentState; - ASEnvironmentTraitCollection oldEnvironmentTraitCollection = environmentState.environmentTraitCollection; + ASPrimitiveTraitCollection oldTraitCollection = self.node.primitiveTraitCollection; - if (ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(environmentTraitCollection, oldEnvironmentTraitCollection) == NO) { - environmentState.environmentTraitCollection = environmentTraitCollection; - self.node.environmentState = environmentState; + if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, oldTraitCollection) == NO) { + self.node.primitiveTraitCollection = traitCollection; - NSArray> *children = [self.node children]; - for (id child in children) { - ASEnvironmentStatePropagateDown(child, environmentState.environmentTraitCollection); + NSArray> *children = [self.node sublayoutElements]; + for (id child in children) { + ASTraitCollectionPropagateDown(child, traitCollection); } - // once we've propagated all the traits, layout this node. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + // Once we've propagated all the traits, layout this node. // Remeasure the node with the latest constrained size – old constrained size may be incorrect. - [self.node layoutThatFits:[self nodeConstrainedSize]]; + [_node layoutThatFits:[self nodeConstrainedSize]]; +#pragma clang diagnostic pop } } @@ -323,18 +299,18 @@ - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { [super traitCollectionDidChange:previousTraitCollection]; - ASEnvironmentTraitCollection environmentTraitCollection = [self environmentTraitCollectionForUITraitCollection:self.traitCollection]; - environmentTraitCollection.containerSize = self.view.bounds.size; - [self progagateNewEnvironmentTraitCollection:environmentTraitCollection]; + ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; + traitCollection.containerSize = self.view.bounds.size; + [self propagateNewTraitCollection:traitCollection]; } - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; - ASEnvironmentTraitCollection traitCollection = self.node.environmentTraitCollection; + ASPrimitiveTraitCollection traitCollection = _node.primitiveTraitCollection; traitCollection.containerSize = self.view.bounds.size; - [self progagateNewEnvironmentTraitCollection:traitCollection]; + [self propagateNewTraitCollection:traitCollection]; } @end diff --git a/AsyncDisplayKit/ASVisibilityProtocols.h b/Source/ASVisibilityProtocols.h similarity index 98% rename from AsyncDisplayKit/ASVisibilityProtocols.h rename to Source/ASVisibilityProtocols.h index 2c098cec2d..836b8bafa6 100644 --- a/AsyncDisplayKit/ASVisibilityProtocols.h +++ b/Source/ASVisibilityProtocols.h @@ -10,9 +10,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASLayoutRangeType.h" - -#import "ASBaseDefines.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/ASVisibilityProtocols.m b/Source/ASVisibilityProtocols.m similarity index 93% rename from AsyncDisplayKit/ASVisibilityProtocols.m rename to Source/ASVisibilityProtocols.m index 6a85a7b4d0..4203bc8060 100644 --- a/AsyncDisplayKit/ASVisibilityProtocols.m +++ b/Source/ASVisibilityProtocols.m @@ -10,7 +10,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASVisibilityProtocols.h" +#import ASLayoutRangeMode ASLayoutRangeModeForVisibilityDepth(NSUInteger visibilityDepth) { diff --git a/Source/AsyncDisplayKit+IGListKitMethods.h b/Source/AsyncDisplayKit+IGListKitMethods.h new file mode 100644 index 0000000000..b8b01a37a9 --- /dev/null +++ b/Source/AsyncDisplayKit+IGListKitMethods.h @@ -0,0 +1,63 @@ +// +// AsyncDisplayKit+IGListKitMethods.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/27/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#if AS_IG_LIST_KIT + +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * If you are using AsyncDisplayKit with IGListKit, you should use + * these methods to provide implementations for methods like + * -cellForItemAtIndex: that don't apply when used with AsyncDisplayKit. + * + * Your section controllers should also conform to @c ASSectionController and your + * supplementary view sources should conform to @c ASSupplementaryNodeSource. + */ + +AS_SUBCLASSING_RESTRICTED +@interface ASIGListSectionControllerMethods : NSObject + +/** + * Call this for your section controller's @c cellForItemAtIndex: method. + */ ++ (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController; + +/** + * Call this for your section controller's @c sizeForItemAtIndex: method. + */ ++ (CGSize)sizeForItemAtIndex:(NSInteger)index; + +@end + +AS_SUBCLASSING_RESTRICTED +@interface ASIGListSupplementaryViewSourceMethods : NSObject + +/** + * Call this for your supplementary source's @c viewForSupplementaryElementOfKind:atIndex: method. + */ ++ (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind + atIndex:(NSInteger)index + sectionController:(IGListSectionController *)sectionController; + +/** + * Call this for your supplementary source's @c sizeForSupplementaryViewOfKind:atIndex: method. + */ ++ (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/Source/AsyncDisplayKit+IGListKitMethods.m b/Source/AsyncDisplayKit+IGListKitMethods.m new file mode 100644 index 0000000000..7e61c2806b --- /dev/null +++ b/Source/AsyncDisplayKit+IGListKitMethods.m @@ -0,0 +1,49 @@ +// +// AsyncDisplayKit+IGListKitMethods.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/27/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#if AS_IG_LIST_KIT + +#import "AsyncDisplayKit+IGListKitMethods.h" +#import +#import + +@implementation ASIGListSectionControllerMethods + ++ (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController +{ + return [sectionController.collectionContext dequeueReusableCellOfClass:[_ASCollectionViewCell class] forSectionController:sectionController atIndex:index]; +} + ++ (CGSize)sizeForItemAtIndex:(NSInteger)index +{ + ASDisplayNodeFailAssert(@"Did not expect %@ to be called.", NSStringFromSelector(_cmd)); + return CGSizeZero; +} + +@end + +@implementation ASIGListSupplementaryViewSourceMethods + ++ (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind + atIndex:(NSInteger)index + sectionController:(IGListSectionController *)sectionController +{ + return [sectionController.collectionContext dequeueReusableSupplementaryViewOfKind:elementKind forSectionController:sectionController class:[UICollectionReusableView class] atIndex:index]; +} + ++ (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index +{ + ASDisplayNodeFailAssert(@"Did not expect %@ to be called.", NSStringFromSelector(_cmd)); + return CGSizeZero; +} + +@end + +#endif // AS_IG_LIST_KIT diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h similarity index 78% rename from AsyncDisplayKit/AsyncDisplayKit.h rename to Source/AsyncDisplayKit.h index 136016e4c3..c0b228827f 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -8,7 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #import +#import #import #import @@ -17,8 +19,10 @@ #import #import #import +#import #import +#import #import #import #import @@ -29,26 +33,41 @@ #import #import #import -#import +#import #import #import #import +#import +#import +#import +#import + +#import +#import + +#if AS_IG_LIST_KIT +#import +#import +#endif + #import #import #import +#import #import #import #import #import -#import +#import #import #import -#import +#import +#import #import #import #import @@ -63,18 +82,18 @@ #import #import +#import +#import #import #import #import #import -#import -#import +#import #import #import #import #import #import -#import #import #import #import @@ -93,6 +112,7 @@ #import #import #import +#import #import #import diff --git a/AsyncDisplayKit/module.modulemap b/Source/AsyncDisplayKit.modulemap similarity index 100% rename from AsyncDisplayKit/module.modulemap rename to Source/AsyncDisplayKit.modulemap diff --git a/Source/Base/ASAssert.h b/Source/Base/ASAssert.h new file mode 100644 index 0000000000..23cff76acc --- /dev/null +++ b/Source/Base/ASAssert.h @@ -0,0 +1,77 @@ +// +// ASAssert.h +// AsyncDisplayKit +// +// 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. +// + +#pragma once + +#import +#import + +#define ASDISPLAYNODE_ASSERTIONS_ENABLED (!defined(NS_BLOCK_ASSERTIONS)) + +/** + * Note: In some cases it would be sufficient to do e.g.: + * ASDisplayNodeAssert(...) NSAssert(__VA_ARGS__) + * but we prefer not to, because we want to match the autocomplete behavior of NSAssert. + * The construction listed above does not show the user what arguments are required and what are optional. + */ + +#define ASDisplayNodeAssert(condition, desc, ...) NSAssert(condition, desc, ##__VA_ARGS__) +#define ASDisplayNodeCAssert(condition, desc, ...) NSCAssert(condition, desc, ##__VA_ARGS__) + +#define ASDisplayNodeAssertNil(condition, desc, ...) ASDisplayNodeAssert((condition) == nil, desc, ##__VA_ARGS__) +#define ASDisplayNodeCAssertNil(condition, desc, ...) ASDisplayNodeCAssert((condition) == nil, desc, ##__VA_ARGS__) + +#define ASDisplayNodeAssertNotNil(condition, desc, ...) ASDisplayNodeAssert((condition) != nil, desc, ##__VA_ARGS__) +#define ASDisplayNodeCAssertNotNil(condition, desc, ...) ASDisplayNodeCAssert((condition) != nil, desc, ##__VA_ARGS__) + +#define ASDisplayNodeAssertImplementedBySubclass() ASDisplayNodeAssert(NO, @"This method must be implemented by subclass %@", [self class]); +#define ASDisplayNodeAssertNotInstantiable() ASDisplayNodeAssert(NO, nil, @"This class is not instantiable."); +#define ASDisplayNodeAssertNotSupported() ASDisplayNodeAssert(NO, nil, @"This method is not supported by class %@", [self class]); + +#define ASDisplayNodeAssertMainThread() ASDisplayNodeAssert(0 != pthread_main_np(), @"This method must be called on the main thread") +#define ASDisplayNodeCAssertMainThread() ASDisplayNodeCAssert(0 != pthread_main_np(), @"This function must be called on the main thread") + +#define ASDisplayNodeAssertNotMainThread() ASDisplayNodeAssert(0 == pthread_main_np(), @"This method must be called off the main thread") +#define ASDisplayNodeCAssertNotMainThread() ASDisplayNodeCAssert(0 == pthread_main_np(), @"This function must be called off the main thread") + +#define ASDisplayNodeAssertFlag(X, desc, ...) ASDisplayNodeAssert((1 == __builtin_popcount(X)), desc, ##__VA_ARGS__) +#define ASDisplayNodeCAssertFlag(X, desc, ...) ASDisplayNodeCAssert((1 == __builtin_popcount(X)), desc, ##__VA_ARGS__) + +#define ASDisplayNodeAssertTrue(condition) ASDisplayNodeAssert((condition), @"Expected %s to be true.", #condition) +#define ASDisplayNodeCAssertTrue(condition) ASDisplayNodeCAssert((condition), @"Expected %s to be true.", #condition) + +#define ASDisplayNodeAssertFalse(condition) ASDisplayNodeAssert(!(condition), @"Expected %s to be false.", #condition) +#define ASDisplayNodeCAssertFalse(condition) ASDisplayNodeCAssert(!(condition), @"Expected %s to be false.", #condition) + +#define ASDisplayNodeFailAssert(desc, ...) ASDisplayNodeAssert(NO, desc, ##__VA_ARGS__) +#define ASDisplayNodeCFailAssert(desc, ...) ASDisplayNodeCAssert(NO, desc, ##__VA_ARGS__) + +#define ASDisplayNodeConditionalAssert(shouldTestCondition, condition, desc, ...) ASDisplayNodeAssert((!(shouldTestCondition) || (condition)), desc, ##__VA_ARGS__) +#define ASDisplayNodeConditionalCAssert(shouldTestCondition, condition, desc, ...) ASDisplayNodeCAssert((!(shouldTestCondition) || (condition)), desc, ##__VA_ARGS__) + +#define ASDisplayNodeCAssertPositiveReal(description, num) ASDisplayNodeCAssert(num >= 0 && num <= CGFLOAT_MAX, @"%@ must be a real positive integer.", description) +#define ASDisplayNodeCAssertInfOrPositiveReal(description, num) ASDisplayNodeCAssert(isinf(num) || (num >= 0 && num <= CGFLOAT_MAX), @"%@ must be infinite or a real positive integer.", description) + +#define ASDisplayNodeErrorDomain @"ASDisplayNodeErrorDomain" +#define ASDisplayNodeNonFatalErrorCode 1 + +#define ASDisplayNodeAssertNonFatal(condition, desc, ...) \ + ASDisplayNodeAssert(condition, desc, ##__VA_ARGS__); \ + if (condition == NO) { \ + ASDisplayNodeNonFatalErrorBlock block = [ASDisplayNode nonFatalErrorBlock]; \ + if (block != nil) { \ + NSDictionary *userInfo = nil; \ + if (desc.length > 0) { \ + userInfo = @{ NSLocalizedDescriptionKey : desc }; \ + } \ + NSError *error = [NSError errorWithDomain:ASDisplayNodeErrorDomain code:ASDisplayNodeNonFatalErrorCode userInfo:userInfo]; \ + block(error); \ + } \ + } diff --git a/Source/Base/ASAvailability.h b/Source/Base/ASAvailability.h new file mode 100644 index 0000000000..7f131ed75e --- /dev/null +++ b/Source/Base/ASAvailability.h @@ -0,0 +1,43 @@ +// +// ASAvailability.h +// AsyncDisplayKit +// +// 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 + +#pragma once + +#ifndef kCFCoreFoundationVersionNumber_iOS_9_0 + #define kCFCoreFoundationVersionNumber_iOS_9_0 1240.10 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_10_0 + #define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00 +#endif + +#define AS_AT_LEAST_IOS9 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0) +#define AS_AT_LEAST_IOS10 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0) + +// If Yoga is available, make it available anywhere we use ASAvailability. +// This reduces Yoga-specific code in other files. +#ifndef YOGA_HEADER_PATH + #define YOGA_HEADER_PATH +#endif + +#ifndef YOGA + #define YOGA __has_include(YOGA_HEADER_PATH) +#endif + +#define AS_PIN_REMOTE_IMAGE __has_include() +#define AS_IG_LIST_KIT __has_include() + +/** + * For IGListKit versions < 3.0, you have to use IGListCollectionView. + * For 3.0 and later, that class is removed and you use UICollectionView. + */ +#define IG_LIST_COLLECTION_VIEW __has_include() diff --git a/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h similarity index 81% rename from Base/ASBaseDefines.h rename to Source/Base/ASBaseDefines.h index 88cc188130..c4e8231a20 100755 --- a/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -10,7 +10,7 @@ #pragma once -#import "ASLog.h" +#import // The C++ compiler mangles C function names. extern "C" { /* your C functions */ } prevents this. // You should wrap all C function prototypes declared in headers with ASDISPLAYNODE_EXTERN_C_BEGIN/END, even if @@ -62,6 +62,14 @@ # endif #endif +#ifndef ASDISPLAYNODE_CONST +# if ASDISPLAYNODE_GNUC (3, 0) +# define ASDISPLAYNODE_CONST __attribute__ ((const)) +# else +# define ASDISPLAYNODE_CONST /* no const */ +# endif +#endif + #ifndef ASDISPLAYNODE_WARN_UNUSED # if ASDISPLAYNODE_GNUC (3, 4) # define ASDISPLAYNODE_WARN_UNUSED __attribute__ ((warn_unused_result)) @@ -183,5 +191,49 @@ #define ASOVERLOADABLE __attribute__((overloadable)) + +#if __has_attribute(noescape) +#define AS_NOESCAPE __attribute__((noescape)) +#else +#define AS_NOESCAPE +#endif + +#if __has_attribute(objc_subclassing_restricted) +#define AS_SUBCLASSING_RESTRICTED __attribute__((objc_subclassing_restricted)) +#else +#define AS_SUBCLASSING_RESTRICTED +#endif + /// Ensure that class is of certain kind -#define ASDynamicCast(x, c) ((c *) ([x isKindOfClass:[c class]] ? x : nil)) +#define ASDynamicCast(x, c) ({ \ + id __val = x;\ + ((c *) ([__val isKindOfClass:[c class]] ? __val : nil));\ +}) + +/** + * Create a new set by mapping `collection` over `work`, ignoring nil. + */ +#define ASSetByFlatMapping(collection, decl, work) ({ \ + NSMutableSet *s = [NSMutableSet set]; \ + for (decl in collection) {\ + id result = work; \ + if (result != nil) { \ + [s addObject:result]; \ + } \ + } \ + s; \ +}) + +/** + * Create a new array by mapping `collection` over `work`, ignoring nil. + */ +#define ASArrayByFlatMapping(collection, decl, work) ({ \ + NSMutableArray *a = [NSMutableArray array]; \ + for (decl in collection) {\ + id result = work; \ + if (result != nil) { \ + [a addObject:result]; \ + } \ + } \ + a; \ +}) diff --git a/Base/ASEqualityHelpers.h b/Source/Base/ASEqualityHelpers.h similarity index 95% rename from Base/ASEqualityHelpers.h rename to Source/Base/ASEqualityHelpers.h index 5373ef19ef..0652c197e1 100644 --- a/Base/ASEqualityHelpers.h +++ b/Source/Base/ASEqualityHelpers.h @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASBaseDefines.h" +#import /** @abstract Correctly equates two objects, including cases where both objects are nil. The latter is a case where `isEqual:` fails. diff --git a/Base/ASLog.h b/Source/Base/ASLog.h similarity index 96% rename from Base/ASLog.h rename to Source/Base/ASLog.h index 9e4a7bebaf..6d061a7f1b 100644 --- a/Base/ASLog.h +++ b/Source/Base/ASLog.h @@ -10,7 +10,6 @@ #pragma once -#import "ASAvailability.h" #define ASMultiplexImageNodeLogDebug(...) #define ASMultiplexImageNodeCLogDebug(...) @@ -19,7 +18,7 @@ #define ASMultiplexImageNodeCLogError(...) // Note: `` only exists in Xcode 8 and later. -#if PROFILE && __has_include("") +#if defined(PROFILE) && __has_include("") #import diff --git a/Source/Debug/AsyncDisplayKit+Debug.h b/Source/Debug/AsyncDisplayKit+Debug.h new file mode 100644 index 0000000000..9bc8395d69 --- /dev/null +++ b/Source/Debug/AsyncDisplayKit+Debug.h @@ -0,0 +1,56 @@ +// +// AsyncDisplayKit+Debug.h +// AsyncDisplayKit +// +// Created by Hannah Troisi on 3/7/16. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASImageNode (Debugging) + +/** + * Enables an ASImageNode debug label that shows the ratio of pixels in the source image to those in + * the displayed bounds (including cropRect). This helps detect excessive image fetching / downscaling, + * as well as upscaling (such as providing a URL not suitable for a Retina device). For dev purposes only. + * Specify YES to show the label on all ASImageNodes with non-1.0x source-to-bounds pixel ratio. + */ +@property (class, nonatomic) BOOL shouldShowImageScalingOverlay; + +@end + +@interface ASControlNode (Debugging) + +/** + * Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only. + * NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!! + * Overlay = translucent GREEN color, + * edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE, + * edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond + * overlay rect, but can't be visualized). + * Specify YES to make this debug feature enabled when messaging the ASControlNode class. + */ +@property (class, nonatomic) BOOL enableHitTestDebug; + +@end + +@interface ASDisplayNode (RangeDebugging) + +/** + * Enable a visualization overlay of the all table/collection tuning parameters. For dev purposes only. + * To use, set this in the AppDelegate --> ASDisplayNode.shouldShowRangeDebugOverlay = YES + */ +@property (class, nonatomic) BOOL shouldShowRangeDebugOverlay; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/AsyncDisplayKit+Debug.m b/Source/Debug/AsyncDisplayKit+Debug.m similarity index 98% rename from AsyncDisplayKit/AsyncDisplayKit+Debug.m rename to Source/Debug/AsyncDisplayKit+Debug.m index c12f16f3e0..21c76e8058 100644 --- a/AsyncDisplayKit/AsyncDisplayKit+Debug.m +++ b/Source/Debug/AsyncDisplayKit+Debug.m @@ -10,12 +10,11 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "AsyncDisplayKit+Debug.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNodeExtras.h" -#import "ASWeakSet.h" -#import "UIImage+ASConvenience.h" +#import +#import #import +#import +#import #import #import #import @@ -242,7 +241,7 @@ - (void)updateWithVisibleRatio:(CGFloat)visibleRatio static BOOL __shouldShowRangeDebugOverlay = NO; -@implementation ASRangeController (Debugging) +@implementation ASDisplayNode (RangeDebugging) + (void)setShouldShowRangeDebugOverlay:(BOOL)show { @@ -254,6 +253,10 @@ + (BOOL)shouldShowRangeDebugOverlay return __shouldShowRangeDebugOverlay; } +@end + +@implementation ASRangeController (DebugInternal) + + (void)layoutDebugOverlayIfNeeded { [[_ASRangeDebugOverlayView sharedInstance] setNeedsLayout]; @@ -307,7 +310,7 @@ + (instancetype)sharedInstance { static _ASRangeDebugOverlayView *__rangeDebugOverlay = nil; - if (!__rangeDebugOverlay && [ASRangeController shouldShowRangeDebugOverlay]) { + if (!__rangeDebugOverlay && ASDisplayNode.shouldShowRangeDebugOverlay) { __rangeDebugOverlay = [[self alloc] initWithFrame:CGRectZero]; [[self keyWindow] addSubview:__rangeDebugOverlay]; } diff --git a/Source/Details/ASAbstractLayoutController.h b/Source/Details/ASAbstractLayoutController.h new file mode 100644 index 0000000000..c79f13e8b3 --- /dev/null +++ b/Source/Details/ASAbstractLayoutController.h @@ -0,0 +1,38 @@ +// +// ASAbstractLayoutController.h +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +ASDISPLAYNODE_EXTERN_C_BEGIN + +FOUNDATION_EXPORT ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection, ASRangeTuningParameters rangeTuningParameters); + +FOUNDATION_EXPORT ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection, ASRangeTuningParameters rangeTuningParameters); + +FOUNDATION_EXPORT CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect, ASRangeTuningParameters tuningParameters, ASScrollDirection scrollableDirections, ASScrollDirection scrollDirection); + +ASDISPLAYNODE_EXTERN_C_END + +@interface ASAbstractLayoutController : NSObject + +@end + +@interface ASAbstractLayoutController (Unavailable) + +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType __unavailable; + +- (void)allIndexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet * _Nullable * _Nullable)displaySet preloadSet:(NSSet * _Nullable * _Nullable)preloadSet __unavailable; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/Source/Details/ASAbstractLayoutController.mm similarity index 50% rename from AsyncDisplayKit/Details/ASAbstractLayoutController.mm rename to Source/Details/ASAbstractLayoutController.mm index 15a1d1afbf..0bf6bd750f 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/Source/Details/ASAbstractLayoutController.mm @@ -8,8 +8,10 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASAbstractLayoutController.h" -#import "ASAssert.h" +#import + +#import + #include extern ASRangeTuningParameters const ASRangeTuningParametersZero = {}; @@ -19,9 +21,70 @@ extern BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningPar return lhs.leadingBufferScreenfuls == rhs.leadingBufferScreenfuls && lhs.trailingBufferScreenfuls == rhs.trailingBufferScreenfuls; } +extern ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection, + ASRangeTuningParameters rangeTuningParameters) +{ + ASDirectionalScreenfulBuffer horizontalBuffer = {0, 0}; + BOOL movingRight = ASScrollDirectionContainsRight(scrollDirection); + + horizontalBuffer.positiveDirection = movingRight ? rangeTuningParameters.leadingBufferScreenfuls + : rangeTuningParameters.trailingBufferScreenfuls; + horizontalBuffer.negativeDirection = movingRight ? rangeTuningParameters.trailingBufferScreenfuls + : rangeTuningParameters.leadingBufferScreenfuls; + return horizontalBuffer; +} + +extern ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection, + ASRangeTuningParameters rangeTuningParameters) +{ + ASDirectionalScreenfulBuffer verticalBuffer = {0, 0}; + BOOL movingDown = ASScrollDirectionContainsDown(scrollDirection); + + verticalBuffer.positiveDirection = movingDown ? rangeTuningParameters.leadingBufferScreenfuls + : rangeTuningParameters.trailingBufferScreenfuls; + verticalBuffer.negativeDirection = movingDown ? rangeTuningParameters.trailingBufferScreenfuls + : rangeTuningParameters.leadingBufferScreenfuls; + return verticalBuffer; +} + +extern CGRect CGRectExpandHorizontally(CGRect rect, ASDirectionalScreenfulBuffer buffer) +{ + CGFloat negativeDirectionWidth = buffer.negativeDirection * rect.size.width; + CGFloat positiveDirectionWidth = buffer.positiveDirection * rect.size.width; + rect.size.width = negativeDirectionWidth + rect.size.width + positiveDirectionWidth; + rect.origin.x -= negativeDirectionWidth; + return rect; +} + +extern CGRect CGRectExpandVertically(CGRect rect, ASDirectionalScreenfulBuffer buffer) +{ + CGFloat negativeDirectionHeight = buffer.negativeDirection * rect.size.height; + CGFloat positiveDirectionHeight = buffer.positiveDirection * rect.size.height; + rect.size.height = negativeDirectionHeight + rect.size.height + positiveDirectionHeight; + rect.origin.y -= negativeDirectionHeight; + return rect; +} + +extern CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect, ASRangeTuningParameters tuningParameters, + ASScrollDirection scrollableDirections, ASScrollDirection scrollDirection) +{ + // Can scroll horizontally - expand the range appropriately + if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { + ASDirectionalScreenfulBuffer horizontalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters); + rect = CGRectExpandHorizontally(rect, horizontalBuffer); + } + + // Can scroll vertically - expand the range appropriately + if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { + ASDirectionalScreenfulBuffer verticalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters); + rect = CGRectExpandVertically(rect, verticalBuffer); + } + + return rect; +} + @interface ASAbstractLayoutController () { std::vector> _tuningParameters; - CGSize _viewportSize; } @end @@ -32,6 +95,7 @@ - (instancetype)init if (!(self = [super init])) { return nil; } + ASDisplayNodeAssert(self.class != [ASAbstractLayoutController class], @"Should never create instances of abstract class ASAbstractLayoutController."); _tuningParameters = std::vector> (ASLayoutRangeModeCount, std::vector (ASLayoutRangeTypeCount)); @@ -103,20 +167,15 @@ - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMo #pragma mark - Abstract Index Path Range Support -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType +- (NSSet *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map { ASDisplayNodeAssertNotSupported(); return nil; } -- (void)setViewportSize:(CGSize)viewportSize -{ - _viewportSize = viewportSize; -} - -- (CGSize)viewportSize +- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet *__autoreleasing _Nullable *)displaySet preloadSet:(NSSet *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map { - return _viewportSize; + ASDisplayNodeAssertNotSupported(); } @end diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.h b/Source/Details/ASBasicImageDownloader.h similarity index 100% rename from AsyncDisplayKit/Details/ASBasicImageDownloader.h rename to Source/Details/ASBasicImageDownloader.h diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm b/Source/Details/ASBasicImageDownloader.mm similarity index 97% rename from AsyncDisplayKit/Details/ASBasicImageDownloader.mm rename to Source/Details/ASBasicImageDownloader.mm index 16a9ee4b8d..dbc633c47d 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm +++ b/Source/Details/ASBasicImageDownloader.mm @@ -8,13 +8,13 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASBasicImageDownloader.h" +#import #import -#import "ASBasicImageDownloaderInternal.h" -#import "ASImageContainerProtocolCategories.h" -#import "ASThread.h" +#import +#import +#import #pragma mark - @@ -266,10 +266,6 @@ - (id)downloadImageWithURL:(NSURL *)URL - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier { - if (!downloadIdentifier) { - return; - } - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:ASBasicImageDownloaderContext.class], @"unexpected downloadIdentifier"); ASBasicImageDownloaderContext *context = (ASBasicImageDownloaderContext *)downloadIdentifier; diff --git a/AsyncDisplayKit/Details/ASBatchContext.h b/Source/Details/ASBatchContext.h similarity index 100% rename from AsyncDisplayKit/Details/ASBatchContext.h rename to Source/Details/ASBatchContext.h diff --git a/AsyncDisplayKit/Details/ASBatchContext.mm b/Source/Details/ASBatchContext.mm similarity index 94% rename from AsyncDisplayKit/Details/ASBatchContext.mm rename to Source/Details/ASBatchContext.mm index 94c148a804..e2a9a9de24 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.mm +++ b/Source/Details/ASBatchContext.mm @@ -8,9 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASBatchContext.h" +#import -#import "ASThread.h" +#import typedef NS_ENUM(NSInteger, ASBatchContextState) { ASBatchContextStateFetching, diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.h b/Source/Details/ASCollectionElement.h similarity index 58% rename from AsyncDisplayKit/Details/ASIndexedNodeContext.h rename to Source/Details/ASCollectionElement.h index 2bf7822221..45102cd0ad 100644 --- a/AsyncDisplayKit/Details/ASIndexedNodeContext.h +++ b/Source/Details/ASCollectionElement.h @@ -1,5 +1,5 @@ // -// ASIndexedNodeContext.h +// ASCollectionElement.h // AsyncDisplayKit // // Created by Huy Nguyen on 2/28/16. @@ -11,27 +11,26 @@ // #import -#import +#import + +@class ASDisplayNode; NS_ASSUME_NONNULL_BEGIN -@interface ASIndexedNodeContext : NSObject +AS_SUBCLASSING_RESTRICTED +@interface ASCollectionElement : NSObject -/** - * The index path at which this node was originally inserted. Don't rely on this - * property too heavily – we should remove it in the future. - */ -@property (nonatomic, readonly, strong) NSIndexPath *indexPath; +//TODO change this to be a generic "kind" or "elementKind" that exposes `nil` for row kind @property (nonatomic, readonly, copy, nullable) NSString *supplementaryElementKind; -@property (nonatomic, readonly, assign) ASSizeRange constrainedSize; -@property (weak, nonatomic) id environment; -@property (nonatomic, readonly, assign) ASEnvironmentTraitCollection environmentTraitCollection; +@property (nonatomic, assign) ASSizeRange constrainedSize; +@property (nonatomic, weak) ASDisplayNode *owningNode; +@property (nonatomic, assign) ASPrimitiveTraitCollection traitCollection; - (instancetype)initWithNodeBlock:(ASCellNodeBlock)nodeBlock - indexPath:(NSIndexPath *)indexPath supplementaryElementKind:(nullable NSString *)supplementaryElementKind constrainedSize:(ASSizeRange)constrainedSize - environment:(id)environment; + owningNode:(ASDisplayNode *)owningNode + traitCollection:(ASPrimitiveTraitCollection)traitCollection; /** * @return The node, running the node block if necessary. The node block will be discarded @@ -44,8 +43,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (strong, readonly, nullable) ASCellNode *nodeIfAllocated; -+ (NSArray *)indexPathsFromContexts:(NSArray *)contexts; - @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.mm b/Source/Details/ASCollectionElement.mm similarity index 50% rename from AsyncDisplayKit/Details/ASIndexedNodeContext.mm rename to Source/Details/ASCollectionElement.mm index 15020eecb1..216a2d2174 100644 --- a/AsyncDisplayKit/Details/ASIndexedNodeContext.mm +++ b/Source/Details/ASCollectionElement.mm @@ -1,5 +1,5 @@ // -// ASIndexedNodeContext.mm +// ASCollectionElement.mm // AsyncDisplayKit // // Created by Huy Nguyen on 2/28/16. @@ -10,38 +10,36 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASIndexedNodeContext.h" -#import "ASEnvironmentInternal.h" -#import "ASCellNode+Internal.h" +#import +#import #import -@interface ASIndexedNodeContext () +@interface ASCollectionElement () /// Required node block used to allocate a cell node. Nil after the first execution. @property (nonatomic, strong) ASCellNodeBlock nodeBlock; @end -@implementation ASIndexedNodeContext { +@implementation ASCollectionElement { std::mutex _lock; ASCellNode *_node; } - (instancetype)initWithNodeBlock:(ASCellNodeBlock)nodeBlock - indexPath:(NSIndexPath *)indexPath - supplementaryElementKind:(nullable NSString *)supplementaryElementKind + supplementaryElementKind:(NSString *)supplementaryElementKind constrainedSize:(ASSizeRange)constrainedSize - environment:(id)environment + owningNode:(ASDisplayNode *)owningNode + traitCollection:(ASPrimitiveTraitCollection)traitCollection { - NSAssert(nodeBlock != nil && indexPath != nil, @"Node block and index path must not be nil"); + NSAssert(nodeBlock != nil, @"Node block must not be nil"); self = [super init]; if (self) { _nodeBlock = nodeBlock; - _indexPath = indexPath; _supplementaryElementKind = [supplementaryElementKind copy]; _constrainedSize = constrainedSize; - _environment = environment; - _environmentTraitCollection = environment.environmentTraitCollection; + _owningNode = owningNode; + _traitCollection = traitCollection; } return self; } @@ -53,13 +51,12 @@ - (ASCellNode *)node ASCellNode *node = _nodeBlock(); _nodeBlock = nil; if (node == nil) { - ASDisplayNodeFailAssert(@"Node block returned nil node! Index path: %@", _indexPath); + ASDisplayNodeFailAssert(@"Node block returned nil node!"); node = [[ASCellNode alloc] init]; } - node.cachedIndexPath = _indexPath; - node.supplementaryElementKind = _supplementaryElementKind; - node.owningNode = (ASDisplayNode *)_environment; - ASEnvironmentStatePropagateDown(node, _environmentTraitCollection); + node.owningNode = _owningNode; + node.collectionElement = self; + ASTraitCollectionPropagateDown(node, _traitCollection); _node = node; } return _node; @@ -71,13 +68,21 @@ - (ASCellNode *)nodeIfAllocated return _node; } -+ (NSArray *)indexPathsFromContexts:(NSArray *)contexts +- (void)setTraitCollection:(ASPrimitiveTraitCollection)traitCollection { - NSMutableArray *result = [NSMutableArray arrayWithCapacity:contexts.count]; - for (ASIndexedNodeContext *ctx in contexts) { - [result addObject:ctx.indexPath]; + ASCellNode *nodeIfNeedsPropagation; + + { + std::lock_guard l(_lock); + if (! ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(_traitCollection, traitCollection)) { + _traitCollection = traitCollection; + nodeIfNeedsPropagation = _node; + } + } + + if (nodeIfNeedsPropagation != nil) { + ASTraitCollectionPropagateDown(nodeIfNeedsPropagation, traitCollection); } - return result; } @end diff --git a/Source/Details/ASCollectionFlowLayoutDelegate.h b/Source/Details/ASCollectionFlowLayoutDelegate.h new file mode 100644 index 0000000000..515435bb75 --- /dev/null +++ b/Source/Details/ASCollectionFlowLayoutDelegate.h @@ -0,0 +1,22 @@ +// +// ASCollectionFlowLayoutDelegate.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 28/2/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED + +@interface ASCollectionFlowLayoutDelegate : NSObject + +- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionFlowLayoutDelegate.m b/Source/Details/ASCollectionFlowLayoutDelegate.m new file mode 100644 index 0000000000..cc21f401ed --- /dev/null +++ b/Source/Details/ASCollectionFlowLayoutDelegate.m @@ -0,0 +1,81 @@ +// ASCollectionFlowLayoutDelegate.m +// AsyncDisplayKit +// +// Created by Huy Nguyen on 28/2/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import +#import + +@implementation ASCollectionFlowLayoutDelegate { + ASScrollDirection _scrollableDirections; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _scrollableDirections = ASScrollDirectionVerticalDirections; + } + return self; +} + +- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections +{ + self = [self init]; + if (self) { + _scrollableDirections = scrollableDirections; + } + return self; +} + +- (ASSizeRange)sizeRangeThatFits:(CGSize)viewportSize +{ + ASSizeRange sizeRange = ASSizeRangeUnconstrained; + if (ASScrollDirectionContainsVerticalDirection(_scrollableDirections) == NO) { + sizeRange.min.height = viewportSize.height; + sizeRange.max.height = viewportSize.height; + } + if (ASScrollDirectionContainsHorizontalDirection(_scrollableDirections) == NO) { + sizeRange.min.width = viewportSize.width; + sizeRange.max.width = viewportSize.width; + } + return sizeRange; +} + +- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements +{ + return nil; +} + +- (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context +{ + ASElementMap *elements = context.elements; + NSMutableArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); + if (children.count == 0) { + return [[ASCollectionLayoutState alloc] initWithElements:elements + contentSize:CGSizeZero + elementToLayoutArrtibutesMap:[NSMapTable weakToStrongObjectsMapTable]]; + } + + ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + flexWrap:ASStackLayoutFlexWrapWrap + alignContent:ASStackLayoutAlignContentStart + children:children]; + stackSpec.concurrent = YES; + ASLayout *layout = [stackSpec layoutThatFits:[self sizeRangeThatFits:context.viewportSize]]; + return [[ASCollectionLayoutState alloc] initWithElements:elements layout:layout]; +} + +@end diff --git a/AsyncDisplayKit/Details/ASCollectionInternal.h b/Source/Details/ASCollectionInternal.h similarity index 88% rename from AsyncDisplayKit/Details/ASCollectionInternal.h rename to Source/Details/ASCollectionInternal.h index 814f51d3b9..b12ff5d55a 100644 --- a/AsyncDisplayKit/Details/ASCollectionInternal.h +++ b/Source/Details/ASCollectionInternal.h @@ -20,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN @class ASRangeController; @interface ASCollectionView () -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator eventLog:(ASEventLog*)eventLog; +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator eventLog:(nullable ASEventLog *)eventLog; @property (nonatomic, weak, readwrite) ASCollectionNode *collectionNode; @property (nonatomic, strong, readonly) ASDataController *dataController; @@ -39,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN * * @param indexPath The index path of the row. */ -- (NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath; +- (nullable NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath; /** * Attempt to get the node index paths given the view-layer index paths. @@ -48,6 +48,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSArray *)convertIndexPathsToCollectionNode:(nullable NSArray *)indexPaths; +- (void)beginUpdates; + +- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASCollectionInternal.m b/Source/Details/ASCollectionInternal.m similarity index 100% rename from AsyncDisplayKit/Details/ASCollectionInternal.m rename to Source/Details/ASCollectionInternal.m diff --git a/Source/Details/ASCollectionLayoutContext.h b/Source/Details/ASCollectionLayoutContext.h new file mode 100644 index 0000000000..dc64a7dc6f --- /dev/null +++ b/Source/Details/ASCollectionLayoutContext.h @@ -0,0 +1,29 @@ +// +// ASCollectionLayoutContext.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 21/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import +#import + +@class ASElementMap; + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED + +@interface ASCollectionLayoutContext : NSObject + +@property (nonatomic, assign, readonly) CGSize viewportSize; +@property (nonatomic, strong, readonly) ASElementMap *elements; +@property (nonatomic, strong, readonly, nullable) id additionalInfo; + +- (instancetype)init __unavailable; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionLayoutContext.mm b/Source/Details/ASCollectionLayoutContext.mm new file mode 100644 index 0000000000..8ba54e25b2 --- /dev/null +++ b/Source/Details/ASCollectionLayoutContext.mm @@ -0,0 +1,59 @@ +// +// ASCollectionLayoutContext.mm +// AsyncDisplayKit +// +// Created by Huy Nguyen on 21/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +#import +#import +#import +#import + +@implementation ASCollectionLayoutContext + +- (instancetype)initWithViewportSize:(CGSize)viewportSize elements:(ASElementMap *)elements additionalInfo:(id)additionalInfo +{ + self = [super init]; + if (self) { + _viewportSize = viewportSize; + _elements = elements; + _additionalInfo = additionalInfo; + } + return self; +} + +- (BOOL)isEqualToContext:(ASCollectionLayoutContext *)context +{ + if (context == nil) { + return NO; + } + return CGSizeEqualToSize(_viewportSize, context.viewportSize) && ASObjectIsEqual(_elements, context.elements) && ASObjectIsEqual(_additionalInfo, context.additionalInfo); +} + +- (BOOL)isEqual:(id)other +{ + if (self == other) { + return YES; + } + if (! [other isKindOfClass:[ASCollectionLayoutContext class]]) { + return NO; + } + return [self isEqualToContext:other]; +} + +- (NSUInteger)hash +{ + NSUInteger subhashes[] = { + ASHashFromCGSize(_viewportSize), + [_elements hash], + [_additionalInfo hash] + }; + return ASIntegerArrayHash(subhashes, sizeof(subhashes) / sizeof(subhashes[0])); +} + +@end diff --git a/Source/Details/ASCollectionLayoutDelegate.h b/Source/Details/ASCollectionLayoutDelegate.h new file mode 100644 index 0000000000..c077578f1b --- /dev/null +++ b/Source/Details/ASCollectionLayoutDelegate.h @@ -0,0 +1,45 @@ +// +// ASCollectionLayoutDelegate.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 21/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +@class ASElementMap, ASCollectionLayoutContext, ASCollectionLayoutState; + +NS_ASSUME_NONNULL_BEGIN + +@protocol ASCollectionLayoutDelegate + +/** + * @abstract Returns any additional information needed for a coming layout pass with the given elements. + * + * @discussion The returned object must support equality and hashing (i.e `-isEqual:` and `-hash` must be properly implemented). + * + * @discussion This method will be called on main thread. + */ +- (nullable id)additionalInfoForLayoutWithElements:(ASElementMap *)elements; + +/** + * @abstract Prepares and returns a new layout for given context. + * + * @param context A context that contains all elements to be laid out and any additional information needed. + * + * @return The new layout calculated for the given context. + * + * @discussion This method is called ahead of time, i.e before the underlying collection/table view is aware of the provided elements. + * As a result, this method should rely solely on the given context and should not reach out to other objects for information not available in the context. + * + * @discussion This method will be called on background theads. It must be thread-safe and should not change any internal state of this object. + * + * @discussion This method must block its calling thread. It can dispatch to other theads to reduce blocking time. + */ +- (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionLayoutState.h b/Source/Details/ASCollectionLayoutState.h new file mode 100644 index 0000000000..031e7f4f7e --- /dev/null +++ b/Source/Details/ASCollectionLayoutState.h @@ -0,0 +1,55 @@ +// +// ASCollectionLayoutState.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 9/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import +#import + +@class ASElementMap, ASCollectionElement, ASLayout; + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED +@interface ASCollectionLayoutState : NSObject + +/// The elements used to calculate this object +@property (nonatomic, strong, readonly) ASElementMap *elements; + +@property (nonatomic, assign, readonly) CGSize contentSize; + +/// Element to layout attributes map. Should use weak pointers for elements. +@property (nonatomic, strong, readonly) NSMapTable *elementToLayoutArrtibutesMap; + +- (instancetype)init __unavailable; + +/** + * Designated initializer. + * + * @param elements The elements used to calculate this object + * + * @param contentSize The content size of the collection's layout + * + * @param elementToLayoutArrtibutesMap Map between elements to their layout attributes. The map may contain all elements, or a subset of them and will be updated later. + * Also, it should have NSMapTableObjectPointerPersonality and NSMapTableWeakMemory as key options. + */ +- (instancetype)initWithElements:(ASElementMap *)elements contentSize:(CGSize)contentSize elementToLayoutArrtibutesMap:(NSMapTable *)attrsMap NS_DESIGNATED_INITIALIZER; + +/** + * Convenience initializer. + * + * @param elements The elements used to calculate this object + * + * @param layout The layout describes size and position of all elements, or a subset of them and will be updated later. + * + * @discussion The sublayouts that describe position of elements must be direct children of the root layout object parameter. + */ +- (instancetype)initWithElements:(ASElementMap *)elements layout:(ASLayout *)layout; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionLayoutState.m b/Source/Details/ASCollectionLayoutState.m new file mode 100644 index 0000000000..b65a471799 --- /dev/null +++ b/Source/Details/ASCollectionLayoutState.m @@ -0,0 +1,52 @@ +// +// ASCollectionLayoutState.m +// AsyncDisplayKit +// +// Created by Huy Nguyen on 9/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import + +@implementation ASCollectionLayoutState + +- (instancetype)initWithElements:(ASElementMap *)elements layout:(ASLayout *)layout +{ + NSMapTable *attrsMap = [NSMapTable mapTableWithKeyOptions:(NSMapTableObjectPointerPersonality | NSMapTableWeakMemory) valueOptions:NSMapTableStrongMemory]; + for (ASLayout *sublayout in layout.sublayouts) { + ASCollectionElement *element = ((ASCellNode *)sublayout.layoutElement).collectionElement; + NSIndexPath *indexPath = [elements indexPathForElement:element]; + NSString *supplementaryElementKind = element.supplementaryElementKind; + + UICollectionViewLayoutAttributes *attrs; + if (supplementaryElementKind == nil) { + attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; + } else { + attrs = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:supplementaryElementKind withIndexPath:indexPath]; + } + + attrs.frame = sublayout.frame; + [attrsMap setObject:attrs forKey:element]; + } + + return [self initWithElements:elements contentSize:layout.size elementToLayoutArrtibutesMap:attrsMap]; +} + +- (instancetype)initWithElements:(ASElementMap *)elements contentSize:(CGSize)contentSize elementToLayoutArrtibutesMap:(NSMapTable *)attrsMap +{ + self = [super init]; + if (self) { + _elements = elements; + _contentSize = contentSize; + _elementToLayoutArrtibutesMap = attrsMap; + } + return self; +} + +@end diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h b/Source/Details/ASCollectionViewLayoutController.h similarity index 93% rename from AsyncDisplayKit/Details/ASCollectionViewLayoutController.h rename to Source/Details/ASCollectionViewLayoutController.h index 4f2457e697..9182f15d37 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h +++ b/Source/Details/ASCollectionViewLayoutController.h @@ -9,12 +9,12 @@ // #import -#import NS_ASSUME_NONNULL_BEGIN @class ASCollectionView; +AS_SUBCLASSING_RESTRICTED @interface ASCollectionViewLayoutController : ASAbstractLayoutController - (instancetype)initWithCollectionView:(ASCollectionView *)collectionView; diff --git a/Source/Details/ASCollectionViewLayoutController.m b/Source/Details/ASCollectionViewLayoutController.m new file mode 100644 index 0000000000..096b069082 --- /dev/null +++ b/Source/Details/ASCollectionViewLayoutController.m @@ -0,0 +1,129 @@ +// +// ASCollectionViewLayoutController.mm +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import +#import +#import +#import +#import + +struct ASRangeGeometry { + CGRect rangeBounds; + CGRect updateBounds; +}; +typedef struct ASRangeGeometry ASRangeGeometry; + + +#pragma mark - +#pragma mark ASCollectionViewLayoutController + +@interface ASCollectionViewLayoutController () +{ + @package + ASCollectionView * __weak _collectionView; + UICollectionViewLayout * __strong _collectionViewLayout; +} +@end + +@implementation ASCollectionViewLayoutController + +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView +{ + if (!(self = [super init])) { + return nil; + } + + _collectionView = collectionView; + _collectionViewLayout = [collectionView collectionViewLayout]; + return self; +} + +- (NSSet *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map +{ + ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; + CGRect rangeBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:tuningParameters]; + return [self elementsWithinRangeBounds:rangeBounds map:map]; +} + +- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet *__autoreleasing _Nullable *)displaySet preloadSet:(NSSet *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map +{ + if (displaySet == NULL || preloadSet == NULL) { + return; + } + + ASRangeTuningParameters displayParams = [self tuningParametersForRangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay]; + ASRangeTuningParameters preloadParams = [self tuningParametersForRangeMode:rangeMode rangeType:ASLayoutRangeTypePreload]; + CGRect displayBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:displayParams]; + CGRect preloadBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:preloadParams]; + + CGRect unionBounds = CGRectUnion(displayBounds, preloadBounds); + NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:unionBounds]; + + NSMutableSet *display = [NSMutableSet setWithCapacity:layoutAttributes.count]; + NSMutableSet *preload = [NSMutableSet setWithCapacity:layoutAttributes.count]; + + for (UICollectionViewLayoutAttributes *la in layoutAttributes) { + // Manually filter out elements that don't intersect the range bounds. + // See comment in elementsForItemsWithinRangeBounds: + // This is re-implemented here so that the iteration over layoutAttributes can be done once to check both ranges. + CGRect frame = la.frame; + BOOL intersectsDisplay = CGRectIntersectsRect(displayBounds, frame); + BOOL intersectsPreload = CGRectIntersectsRect(preloadBounds, frame); + if (intersectsDisplay == NO && intersectsPreload == NO && CATransform3DIsIdentity(la.transform3D) == YES) { + // Questionable why the element would be included here, but it doesn't belong. + continue; + } + + // Avoid excessive retains and releases, as well as property calls. We know the element is kept alive by map. + __unsafe_unretained ASCollectionElement *e = [map elementForLayoutAttributes:la]; + if (e != nil && intersectsDisplay) { + [display addObject:e]; + } + if (e != nil && intersectsPreload) { + [preload addObject:e]; + } + } + + *displaySet = display; + *preloadSet = preload; + return; +} + +- (NSSet *)elementsWithinRangeBounds:(CGRect)rangeBounds map:(ASElementMap *)map +{ + NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds]; + NSMutableSet *elementSet = [NSMutableSet setWithCapacity:layoutAttributes.count]; + + for (UICollectionViewLayoutAttributes *la in layoutAttributes) { + // Manually filter out elements that don't intersect the range bounds. + // If a layout returns elements outside the requested rect this can be a huge problem. + // For instance in a paging flow, you may only want to preload 3 pages (one center, one on each side) + // but if flow layout includes the 4th page (which it does! as of iOS 9&10), you will preload a 4th + // page as well. + if (CATransform3DIsIdentity(la.transform3D) && CGRectIntersectsRect(la.frame, rangeBounds) == NO) { + continue; + } + [elementSet addObject:[map elementForLayoutAttributes:la]]; + } + + return elementSet; +} + +- (CGRect)rangeBoundsWithScrollDirection:(ASScrollDirection)scrollDirection + rangeTuningParameters:(ASRangeTuningParameters)tuningParameters +{ + CGRect rect = _collectionView.bounds; + + return CGRectExpandToRangeWithScrollableDirections(rect, tuningParameters, [_collectionView scrollableDirections], scrollDirection); +} + +@end diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.h b/Source/Details/ASCollectionViewLayoutInspector.h similarity index 89% rename from AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.h rename to Source/Details/ASCollectionViewLayoutInspector.h index 5b8c901218..983106d8e9 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.h +++ b/Source/Details/ASCollectionViewLayoutInspector.h @@ -70,13 +70,15 @@ extern ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *colle /** * A layout inspector for non-flow layouts that returns a constrained size to let the cells layout itself as - * far as possible based on the scrollable direction of the collection view. It throws exceptions for delegate - * methods that are related to supplementary node's management. + * far as possible based on the scrollable direction of the collection view. + * It doesn't support supplementary nodes and therefore doesn't implement delegate methods + * that are related to supplementary node's management. + * + * @warning This class is not meant to be subclassed and will be restricted in the future. */ @interface ASCollectionViewLayoutInspector : NSObject -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Use -init instead."); @end diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.m b/Source/Details/ASCollectionViewLayoutInspector.m similarity index 73% rename from AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.m rename to Source/Details/ASCollectionViewLayoutInspector.m index 960619b464..b10b976cc8 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.m +++ b/Source/Details/ASCollectionViewLayoutInspector.m @@ -6,10 +6,11 @@ // Copyright © 2016 Facebook. All rights reserved. // -#import "ASCollectionViewLayoutInspector.h" +#import -#import "ASCollectionView.h" -#import "ASCollectionView+Undeprecated.h" +#import +#import +#import #pragma mark - Helper Functions @@ -38,11 +39,7 @@ @implementation ASCollectionViewLayoutInspector { - (instancetype)initWithCollectionView:(ASCollectionView *)collectionView { - self = [super init]; - if (self != nil) { - [self didChangeCollectionViewDelegate:collectionView.asyncDelegate]; - } - return self; + return [self init]; } #pragma mark ASCollectionViewLayoutInspecting @@ -79,16 +76,4 @@ - (ASScrollDirection)scrollableDirections return ASScrollDirectionNone; } -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - return ASSizeRangeMake(CGSizeZero, CGSizeZero); -} - -- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section -{ - ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - return 0; -} - @end diff --git a/Source/Details/ASDataController.h b/Source/Details/ASDataController.h new file mode 100644 index 0000000000..82b4664cee --- /dev/null +++ b/Source/Details/ASDataController.h @@ -0,0 +1,252 @@ +// +// ASDataController.h +// AsyncDisplayKit +// +// 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. +// + +#pragma once + +#import +#import +#import +#import +#ifdef __cplusplus +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +#if ASEVENTLOG_ENABLE +#define ASDataControllerLogEvent(dataController, ...) [dataController.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__] +#else +#define ASDataControllerLogEvent(dataController, ...) +#endif + +@class ASCellNode; +@class ASCollectionElement; +@class ASDataController; +@class ASElementMap; +@class ASLayout; +@class _ASHierarchyChangeSet; +@protocol ASTraitEnvironment; +@protocol ASSectionContext; + +typedef NSUInteger ASDataControllerAnimationOptions; + +extern NSString * const ASDataControllerRowNodeKind; +extern NSString * const ASCollectionInvalidUpdateException; + +/** + Data source for data controller + It will be invoked in the same thread as the api call of ASDataController. + */ + +@protocol ASDataControllerSource + +/** + Fetch the ASCellNode block for specific index path. This block should return the ASCellNode for the specified index path. + */ +- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath; + +/** + Fetch the number of rows in specific section. + */ +- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section; + +/** + Fetch the number of sections. + */ +- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController; + +/** + Returns if the collection element size matches a given size + */ +- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size; + +@optional + +/** + The constrained size range for layout. Called only if collection layout delegate is not provided. + */ +- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; + +- (NSArray *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections; + +- (NSUInteger)dataController:(ASDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; + +- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + +/** + The constrained size range for layout. Called only if no data controller layout delegate is provided. + */ +- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + +- (nullable id)dataController:(ASDataController *)dataController contextForSection:(NSInteger)section; + +@end + +@protocol ASDataControllerEnvironmentDelegate + +- (nullable id)dataControllerEnvironment; + +@end + +/** + Delegate for notify the data updating of data controller. + These methods will be invoked from main thread right now, but it may be moved to background thread in the future. + */ +@protocol ASDataControllerDelegate + +/** + * Called before updating with given change set. + * + * @param changeSet The change set that includes all updates + */ +- (void)dataController:(ASDataController *)dataController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet; + +/** + * Called for change set updates. + * + * @param changeSet The change set that includes all updates + */ +- (void)dataController:(ASDataController *)dataController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet; + +@end + +@protocol ASDataControllerLayoutDelegate + +/** + * @abstract Returns a layout context needed for a coming layout pass with the given elements. + * The context should contain the elements and any additional information needed. + * + * @discussion This method will be called on main thread. + */ +- (id)layoutContextWithElements:(ASElementMap *)elements; + +/** + * @abstract Prepares in advance a new layout with the given context. + * + * @param context A context that was previously returned by `-layoutContextWithElements:`. + * + * @discussion This method is called ahead of time, i.e before the underlying collection/table view is aware of the provided elements. + * As a result, this method should rely solely on the given context and should not reach out to its collection/table view for information regarding items. + * + * @discussion This method will be called on background theads. It must be thread-safe and should not change any internal state of the conforming object. + * It's recommended to put the resulting layouts of this method into a thread-safe cache that can be looked up later on. + * + * @discussion This method must block its calling thread. It can dispatch to other theads to reduce blocking time. + */ +- (void)prepareLayoutWithContext:(id)context; + +@end + +/** + * Controller to layout data in background, and managed data updating. + * + * All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the data + * will be updated asynchronously. The dataSource must be updated to reflect the changes before these methods has been called. + * For each data updating, the corresponding methods in delegate will be called. + */ +@interface ASDataController : NSObject + +- (instancetype)initWithDataSource:(id)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; + +/** + * The map that is currently displayed. The "UIKit index space." + */ +@property (nonatomic, strong, readonly) ASElementMap *visibleMap; + +/** + * The latest map fetched from the data source. May be more recent than @c visibleMap. + */ +@property (nonatomic, strong, readonly) ASElementMap *pendingMap; + +/** + Data source for fetching data info. + */ +@property (nonatomic, weak, readonly) id dataSource; + +/** + An object that will be included in the backtrace of any update validation exceptions that occur. + */ +@property (nonatomic, weak) id validationErrorSource; + +/** + Delegate to notify when data is updated. + */ +@property (nonatomic, weak) id delegate; + +/** + * + */ +@property (nonatomic, weak) id environmentDelegate; + +/** + * Delegate for preparing layouts. Main thead only. + */ +@property (nonatomic, weak) id layoutDelegate; + +#ifdef __cplusplus +/** + * Returns the most recently gathered item counts from the data source. If the counts + * have been invalidated, this synchronously queries the data source and saves the result. + * + * This must be called on the main thread. + */ +- (std::vector)itemCountsFromDataSource; +#endif + +/** + * Returns YES if reloadData has been called at least once. Before this point it is + * important to ignore/suppress some operations. For example, inserting a section + * before the initial data load should have no effect. + * + * This must be called on the main thread. + */ +@property (nonatomic, readonly) BOOL initialReloadDataHasBeenCalled; + +#if ASEVENTLOG_ENABLE +/* + * @abstract The primitive event tracing object. You shouldn't directly use it to log event. Use the ASDataControllerLogEvent macro instead. + */ +@property (nonatomic, strong, readonly) ASEventLog *eventLog; +#endif + +/** @name Data Updating */ + +- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet; + +/** + * Re-measures all loaded nodes in the backing store. + * + * @discussion Used to respond to a change in size of the containing view + * (e.g. ASTableView or ASCollectionView after an orientation change). + */ +- (void)relayoutAllNodes; + +/** + * Re-measures given nodes in the backing store. + * + * @discussion Used to respond to setNeedsLayout calls in ASCellNode + */ +- (void)relayoutNodes:(id)nodes nodesSizeChanged:(NSMutableArray * _Nonnull)nodesSizesChanged; + +- (void)waitUntilAllUpdatesAreCommitted; + +/** + * Notifies the data controller object that its environment has changed. The object will request its environment delegate for new information + * and propagate the information to all visible elements, including ones that are being prepared in background. + * + * @discussion If called before the initial @c reloadData, this method will do nothing and the trait collection of the initial load will be requested from the environment delegate. + * + * @discussion This method can be called on any threads. + */ +- (void)environmentDidChange; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm new file mode 100644 index 0000000000..8eb0be4ca0 --- /dev/null +++ b/Source/Details/ASDataController.mm @@ -0,0 +1,821 @@ +// +// ASDataController.mm +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import +#import +#import +#import + +//#define LOG(...) NSLog(__VA_ARGS__) +#define LOG(...) + +#define AS_MEASURE_AVOIDED_DATACONTROLLER_WORK 0 + +#define RETURN_IF_NO_DATASOURCE(val) if (_dataSource == nil) { return val; } +#define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd)) + +const static NSUInteger kASDataControllerSizingCountPerProcessor = 5; +const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey"; +const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext"; + +NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; +NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdateException"; + +typedef void (^ASDataControllerCompletionBlock)(NSArray *elements, NSArray *nodes); + +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK +@interface ASDataController (AvoidedWorkMeasuring) ++ (void)_didLayoutNode; ++ (void)_expectToInsertNodes:(NSUInteger)count; +@end +#endif + +@interface ASDataController () { + id _layoutDelegate; + + NSInteger _nextSectionID; + + BOOL _itemCountsFromDataSourceAreValid; // Main thread only. + std::vector _itemCountsFromDataSource; // Main thread only. + + ASMainSerialQueue *_mainSerialQueue; + + dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. + dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting. + + BOOL _initialReloadDataHasBeenCalled; + + struct { + unsigned int supplementaryNodeKindsInSections:1; + unsigned int supplementaryNodesOfKindInSection:1; + unsigned int supplementaryNodeBlockOfKindAtIndexPath:1; + unsigned int constrainedSizeForNodeAtIndexPath:1; + unsigned int constrainedSizeForSupplementaryNodeOfKindAtIndexPath:1; + unsigned int contextForSection:1; + } _dataSourceFlags; +} + +@end + +@implementation ASDataController + +#pragma mark - Lifecycle + +- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog +{ + if (!(self = [super init])) { + return nil; + } + + _dataSource = dataSource; + + _dataSourceFlags.supplementaryNodeKindsInSections = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeKindsInSections:)]; + _dataSourceFlags.supplementaryNodesOfKindInSection = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodesOfKind:inSection:)]; + _dataSourceFlags.supplementaryNodeBlockOfKindAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; + _dataSourceFlags.constrainedSizeForNodeAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:constrainedSizeForNodeAtIndexPath:)]; + _dataSourceFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:)]; + _dataSourceFlags.contextForSection = [_dataSource respondsToSelector:@selector(dataController:contextForSection:)]; + +#if ASEVENTLOG_ENABLE + _eventLog = eventLog; +#endif + + _visibleMap = _pendingMap = [[ASElementMap alloc] init]; + + _nextSectionID = 0; + + _mainSerialQueue = [[ASMainSerialQueue alloc] init]; + + const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding]; + _editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); + dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL); + _editingTransactionGroup = dispatch_group_create(); + + return self; +} + +- (instancetype)init +{ + ASDisplayNodeFailAssert(@"Failed to call designated initializer."); + id fakeDataSource = nil; + ASEventLog *eventLog = nil; + return [self initWithDataSource:fakeDataSource eventLog:eventLog]; +} + ++ (NSUInteger)parallelProcessorCount +{ + static NSUInteger parallelProcessorCount; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + parallelProcessorCount = [[NSProcessInfo processInfo] activeProcessorCount]; + }); + + return parallelProcessorCount; +} + +- (id)layoutDelegate +{ + ASDisplayNodeAssertMainThread(); + return _layoutDelegate; +} + +- (void)setLayoutDelegate:(id)layoutDelegate +{ + ASDisplayNodeAssertMainThread(); + if (layoutDelegate != _layoutDelegate) { + _layoutDelegate = layoutDelegate; + } +} + +#pragma mark - Cell Layout + +- (void)batchAllocateNodesFromElements:(NSArray *)elements andLayout:(BOOL)shouldLayout batchSize:(NSInteger)batchSize batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler +{ + ASSERT_ON_EDITING_QUEUE; +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK + [ASDataController _expectToInsertNodes:elements.count]; +#endif + + if (elements.count == 0 || _dataSource == nil) { + batchCompletionHandler(@[], @[]); + return; + } + + ASProfilingSignpostStart(2, _dataSource); + + if (batchSize == 0) { + batchSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; + } + NSUInteger count = elements.count; + + // Processing in batches + for (NSUInteger i = 0; i < count; i += batchSize) { + NSRange batchedRange = NSMakeRange(i, MIN(count - i, batchSize)); + NSArray *batchedElements = [elements subarrayWithRange:batchedRange]; + NSArray *nodes = [self _allocateNodesFromElements:batchedElements andLayout:shouldLayout]; + batchCompletionHandler(batchedElements, nodes); + } + + ASProfilingSignpostEnd(2, _dataSource); +} + +/** + * Measure and layout the given node with the constrained size range. + */ +- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize +{ + ASDisplayNodeAssert(ASSizeRangeHasSignificantArea(constrainedSize), @"Attempt to layout cell node with invalid size range %@", NSStringFromASSizeRange(constrainedSize)); + + CGRect frame = CGRectZero; + frame.size = [node layoutThatFits:constrainedSize].size; + node.frame = frame; +} + +// TODO Is returned array still needed? Can it be removed? +- (NSArray *)_allocateNodesFromElements:(NSArray *)elements andLayout:(BOOL)shouldLayout +{ + ASSERT_ON_EDITING_QUEUE; + + NSUInteger nodeCount = elements.count; + if (!nodeCount || _dataSource == nil) { + return @[]; + } + + __strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(nodeCount, sizeof(ASCellNode *)); + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + ASDispatchApply(nodeCount, queue, 0, ^(size_t i) { + RETURN_IF_NO_DATASOURCE(); + + // Allocate the node. + ASCollectionElement *context = elements[i]; + ASCellNode *node = context.node; + if (node == nil) { + ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); + node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. + } + + if (shouldLayout) { + // Layout the node if the size range is valid. + ASSizeRange sizeRange = context.constrainedSize; + if (ASSizeRangeHasSignificantArea(sizeRange)) { + [self _layoutNode:node withConstrainedSize:sizeRange]; + } + +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK + [ASDataController _didLayoutNode]; +#endif + } + + allocatedNodeBuffer[i] = node; + }); + + BOOL canceled = _dataSource == nil; + + // Create nodes array + NSArray *nodes = canceled ? nil : [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount]; + + // Nil out buffer indexes to allow arc to free the stored cells. + for (int i = 0; i < nodeCount; i++) { + allocatedNodeBuffer[i] = nil; + } + free(allocatedNodeBuffer); + + return nodes; +} + +#pragma mark - Data Source Access (Calling _dataSource) + +- (NSArray *)_allIndexPathsForItemsOfKind:(NSString *)kind inSections:(NSIndexSet *)sections +{ + ASDisplayNodeAssertMainThread(); + + if (sections.count == 0 || _dataSource == nil) { + return @[]; + } + + NSMutableArray *indexPaths = [NSMutableArray array]; + if ([kind isEqualToString:ASDataControllerRowNodeKind]) { + std::vector counts = [self itemCountsFromDataSource]; + [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { + for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { + NSUInteger itemCount = counts[sectionIndex]; + for (NSUInteger i = 0; i < itemCount; i++) { + [indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:sectionIndex]]; + } + } + }]; + } else if (_dataSourceFlags.supplementaryNodesOfKindInSection) { + [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { + for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { + NSUInteger itemCount = [_dataSource dataController:self supplementaryNodesOfKind:kind inSection:sectionIndex]; + for (NSUInteger i = 0; i < itemCount; i++) { + [indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:sectionIndex]]; + } + } + }]; + } + + return indexPaths; +} + +/** + * Agressively repopulates supplementary nodes of all kinds for sections that contains some given index paths. + * + * @param map The element map into which to apply the change. + * @param indexPaths The index paths belongs to sections whose supplementary nodes need to be repopulated. + * @param changeSet The changeset that triggered this repopulation. + * @param owningNode The node that owns the new elements. + * @param traitCollection The trait collection needed to initialize elements + * @param indexPathsAreNew YES if index paths are "after the update," NO otherwise. + * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source + */ +- (void)_repopulateSupplementaryNodesIntoMap:(ASMutableElementMap *)map + forSectionsContainingIndexPaths:(NSArray *)indexPaths + changeSet:(_ASHierarchyChangeSet *)changeSet + owningNode:(ASDisplayNode *)owningNode + traitCollection:(ASPrimitiveTraitCollection)traitCollection + indexPathsAreNew:(BOOL)indexPathsAreNew + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges +{ + ASDisplayNodeAssertMainThread(); + + if (indexPaths.count == 0) { + return; + } + + // Remove all old supplementaries from these sections + NSIndexSet *oldSections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths]; + [map removeSupplementaryElementsInSections:oldSections]; + + // Add in new ones with the new kinds. + NSIndexSet *newSections; + if (indexPathsAreNew) { + newSections = oldSections; + } else { + newSections = [oldSections as_indexesByMapping:^NSUInteger(NSUInteger oldSection) { + return [changeSet newSectionForOldSection:oldSection]; + }]; + } + + for (NSString *kind in [self supplementaryKindsInSections:newSections]) { + [self _insertElementsIntoMap:map kind:kind forSections:newSections owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + } +} + +/** + * Inserts new elements of a certain kind for some sections + * + * @param kind The kind of the elements, e.g ASDataControllerRowNodeKind + * @param sections The sections that should be populated by new elements + * @param owningNode The node that owns the new elements. + * @param traitCollection The trait collection needed to initialize elements + * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source + */ +- (void)_insertElementsIntoMap:(ASMutableElementMap *)map + kind:(NSString *)kind + forSections:(NSIndexSet *)sections + owningNode:(ASDisplayNode *)owningNode + traitCollection:(ASPrimitiveTraitCollection)traitCollection + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges +{ + ASDisplayNodeAssertMainThread(); + + if (sections.count == 0 || _dataSource == nil) { + return; + } + + NSArray *indexPaths = [self _allIndexPathsForItemsOfKind:kind inSections:sections]; + [self _insertElementsIntoMap:map kind:kind atIndexPaths:indexPaths owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; +} + +/** + * Inserts new elements of a certain kind at some index paths + * + * @param map The map to insert the elements into. + * @param kind The kind of the elements, e.g ASDataControllerRowNodeKind + * @param indexPaths The index paths at which new elements should be populated + * @param owningNode The node that owns the new elements. + * @param traitCollection The trait collection needed to initialize elements + * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source + */ +- (void)_insertElementsIntoMap:(ASMutableElementMap *)map + kind:(NSString *)kind + atIndexPaths:(NSArray *)indexPaths + owningNode:(ASDisplayNode *)owningNode + traitCollection:(ASPrimitiveTraitCollection)traitCollection + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges +{ + ASDisplayNodeAssertMainThread(); + + if (indexPaths.count == 0 || _dataSource == nil) { + return; + } + + BOOL isRowKind = [kind isEqualToString:ASDataControllerRowNodeKind]; + if (!isRowKind && !_dataSourceFlags.supplementaryNodeBlockOfKindAtIndexPath) { + // Populating supplementary elements but data source doesn't support. + return; + } + + LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths); + for (NSIndexPath *indexPath in indexPaths) { + ASCellNodeBlock nodeBlock; + if (isRowKind) { + nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + } else { + nodeBlock = [_dataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; + } + + ASSizeRange constrainedSize; + if (shouldFetchSizeRanges) { + constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + } + + ASCollectionElement *element = [[ASCollectionElement alloc] initWithNodeBlock:nodeBlock + supplementaryElementKind:isRowKind ? nil : kind + constrainedSize:constrainedSize + owningNode:owningNode + traitCollection:traitCollection]; + [map insertElement:element atIndexPath:indexPath]; + } +} + +- (void)invalidateDataSourceItemCounts +{ + ASDisplayNodeAssertMainThread(); + _itemCountsFromDataSourceAreValid = NO; +} + +- (std::vector)itemCountsFromDataSource +{ + ASDisplayNodeAssertMainThread(); + if (NO == _itemCountsFromDataSourceAreValid) { + id source = self.dataSource; + NSInteger sectionCount = [source numberOfSectionsInDataController:self]; + std::vector newCounts; + newCounts.reserve(sectionCount); + for (NSInteger i = 0; i < sectionCount; i++) { + newCounts.push_back([source dataController:self rowsInSection:i]); + } + _itemCountsFromDataSource = newCounts; + _itemCountsFromDataSourceAreValid = YES; + } + return _itemCountsFromDataSource; +} + +- (NSArray *)supplementaryKindsInSections:(NSIndexSet *)sections +{ + if (_dataSourceFlags.supplementaryNodeKindsInSections) { + return [_dataSource dataController:self supplementaryNodeKindsInSections:sections]; + } + + return @[]; +} + +- (ASSizeRange)constrainedSizeForElement:(ASCollectionElement *)element inElementMap:(ASElementMap *)map +{ + ASDisplayNodeAssertMainThread(); + NSString *kind = element.supplementaryElementKind ?: ASDataControllerRowNodeKind; + NSIndexPath *indexPath = [map indexPathForElement:element]; + return [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; +} + + +- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + ASDisplayNodeAssertMainThread(); + + id dataSource = _dataSource; + if (dataSource == nil) { + return ASSizeRangeZero; + } + + if ([kind isEqualToString:ASDataControllerRowNodeKind]) { + ASDisplayNodeAssert(_dataSourceFlags.constrainedSizeForNodeAtIndexPath, @"-dataController:constrainedSizeForNodeAtIndexPath: must also be implemented"); + return [dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]; + } + + if (_dataSourceFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath){ + return [dataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; + } + + ASDisplayNodeAssert(NO, @"Unknown constrained size for node of kind %@ by data source %@", kind, dataSource); + return ASSizeRangeZero; +} + +#pragma mark - Batching (External API) + +- (void)waitUntilAllUpdatesAreCommitted +{ + // Schedule block in main serial queue to wait until all operations are finished that are + // where scheduled while waiting for the _editingTransactionQueue to finish + [self _scheduleBlockOnMainSerialQueue:^{ }]; +} + +- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet +{ + ASDisplayNodeAssertMainThread(); + + if (changeSet.includesReloadData) { + _initialReloadDataHasBeenCalled = YES; + } + + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + + // If the initial reloadData has not been called, just bail because we don't have our old data source counts. + // See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable + // for the issue that UICollectionView has that we're choosing to workaround. + if (!_initialReloadDataHasBeenCalled) { + [changeSet executeCompletionHandlerWithFinished:YES]; + return; + } + + [self invalidateDataSourceItemCounts]; + + // Log events + ASDataControllerLogEvent(self, @"triggeredUpdate: %@", changeSet); +#if ASEVENTLOG_ENABLE + NSString *changeSetDescription = ASObjectDescriptionMakeTiny(changeSet); + [changeSet addCompletionHandler:^(BOOL finished) { + ASDataControllerLogEvent(self, @"finishedUpdate: %@", changeSetDescription); + }]; +#endif + + // Attempt to mark the update completed. This is when update validation will occur inside the changeset. + // If an invalid update exception is thrown, we catch it and inject our "validationErrorSource" object, + // which is the table/collection node's data source, into the exception reason to help debugging. + @try { + [changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]]; + } @catch (NSException *e) { + id responsibleDataSource = self.validationErrorSource; + if (e.name == ASCollectionInvalidUpdateException && responsibleDataSource != nil) { + [NSException raise:ASCollectionInvalidUpdateException format:@"%@: %@", [responsibleDataSource class], e.reason]; + } else { + @throw e; + } + } + + // Since we waited for _editingTransactionGroup at the beginning of this method, at this point we can guarantee that _pendingMap equals to _visibleMap. + // So if the change set is empty, we don't need to modify data and can safely schedule to notify the delegate. + if (changeSet.isEmpty) { + [_mainSerialQueue performBlockOnMainThread:^{ + [_delegate dataController:self willUpdateWithChangeSet:changeSet]; + [_delegate dataController:self didUpdateWithChangeSet:changeSet]; + }]; + return; + } + + // Mutable copy of current data. + ASMutableElementMap *mutableMap = [_pendingMap mutableCopy]; + + BOOL canDelegateLayout = (_layoutDelegate != nil); + + // Step 1: Update the mutable copies to match the data source's state + [self _updateSectionContextsInMap:mutableMap changeSet:changeSet]; + __weak id environment = [self.environmentDelegate dataControllerEnvironment]; + __weak ASDisplayNode *owningNode = (ASDisplayNode *)environment; // This is gross! + ASPrimitiveTraitCollection existingTraitCollection = [environment primitiveTraitCollection]; + [self _updateElementsInMap:mutableMap changeSet:changeSet owningNode:owningNode traitCollection:existingTraitCollection shouldFetchSizeRanges:(! canDelegateLayout)]; + + // Step 2: Clone the new data + ASElementMap *newMap = [mutableMap copy]; + _pendingMap = newMap; + + // Step 3: Ask layout delegate for contexts + id layoutContext = nil; + if (canDelegateLayout) { + layoutContext = [_layoutDelegate layoutContextWithElements:newMap]; + } + + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + // Step 4: Allocate and layout elements if can't delegate + NSArray *elementsToProcess; + if (canDelegateLayout) { + // Allocate all nodes before handling them to the layout delegate. + // In the future, we may want to let the delegate drive allocation as well. + elementsToProcess = ASArrayByFlatMapping(newMap, + ASCollectionElement *element, + (element.nodeIfAllocated == nil ? element : nil)); + } else { + elementsToProcess = ASArrayByFlatMapping(newMap, + ASCollectionElement *element, + (element.nodeIfAllocated.calculatedLayout == nil ? element : nil)); + } + + [self batchAllocateNodesFromElements:elementsToProcess andLayout:(! canDelegateLayout) batchSize:elementsToProcess.count batchCompletion:^(NSArray *elements, NSArray *nodes) { + ASSERT_ON_EDITING_QUEUE; + + if (canDelegateLayout) { + [_layoutDelegate prepareLayoutWithContext:layoutContext]; + } + + [_mainSerialQueue performBlockOnMainThread:^{ + [_delegate dataController:self willUpdateWithChangeSet:changeSet]; + + // Step 5: Deploy the new data as "completed" and inform delegate + _visibleMap = newMap; + + [_delegate dataController:self didUpdateWithChangeSet:changeSet]; + }]; + }]; + }); +} + +/** + * Update sections based on the given change set. + */ +- (void)_updateSectionContextsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyChangeSet *)changeSet +{ + ASDisplayNodeAssertMainThread(); + + if (!_dataSourceFlags.contextForSection) { + return; + } + + if (changeSet.includesReloadData) { + [map removeAllSectionContexts]; + + NSUInteger sectionCount = [self itemCountsFromDataSource].size(); + NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; + [self _insertSectionContextsIntoMap:map indexes:sectionIndexes]; + // Return immediately because reloadData can't be used in conjuntion with other updates. + return; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { + [map removeSectionContextsAtIndexes:change.indexSet]; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { + [self _insertSectionContextsIntoMap:map indexes:change.indexSet]; + } +} + +- (void)_insertSectionContextsIntoMap:(ASMutableElementMap *)map indexes:(NSIndexSet *)sectionIndexes +{ + ASDisplayNodeAssertMainThread(); + + if (!_dataSourceFlags.contextForSection) { + return; + } + + [sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + id context = [_dataSource dataController:self contextForSection:idx]; + ASSection *section = [[ASSection alloc] initWithSectionID:_nextSectionID context:context]; + [map insertSection:section atIndex:idx]; + _nextSectionID++; + }]; +} + +/** + * Update elements based on the given change set. + */ +- (void)_updateElementsInMap:(ASMutableElementMap *)map + changeSet:(_ASHierarchyChangeSet *)changeSet + owningNode:(ASDisplayNode *)owningNode + traitCollection:(ASPrimitiveTraitCollection)traitCollection + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges +{ + ASDisplayNodeAssertMainThread(); + + if (changeSet.includesReloadData) { + [map removeAllElements]; + + NSUInteger sectionCount = [self itemCountsFromDataSource].size(); + if (sectionCount > 0) { + NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; + [self _insertElementsIntoMap:map sections:sectionIndexes owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + } + // Return immediately because reloadData can't be used in conjuntion with other updates. + return; + } + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { + [map removeItemsAtIndexPaths:change.indexPaths]; + // Aggressively repopulate supplementary nodes (#1773 & #1629) + [self _repopulateSupplementaryNodesIntoMap:map forSectionsContainingIndexPaths:change.indexPaths + changeSet:changeSet + owningNode:owningNode + traitCollection:traitCollection + indexPathsAreNew:NO + shouldFetchSizeRanges:shouldFetchSizeRanges]; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { + NSIndexSet *sectionIndexes = change.indexSet; + [map removeSupplementaryElementsInSections:sectionIndexes]; + [map removeSectionsOfItems:sectionIndexes]; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { + [self _insertElementsIntoMap:map sections:change.indexSet owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + } + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { + [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind atIndexPaths:change.indexPaths owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + // Aggressively reload supplementary nodes (#1773 & #1629) + [self _repopulateSupplementaryNodesIntoMap:map forSectionsContainingIndexPaths:change.indexPaths + changeSet:changeSet + owningNode:owningNode + traitCollection:traitCollection + indexPathsAreNew:YES + shouldFetchSizeRanges:shouldFetchSizeRanges]; + } +} + +- (void)_insertElementsIntoMap:(ASMutableElementMap *)map + sections:(NSIndexSet *)sectionIndexes + owningNode:(ASDisplayNode *)owningNode + traitCollection:(ASPrimitiveTraitCollection)traitCollection + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges +{ + ASDisplayNodeAssertMainThread(); + + if (sectionIndexes.count == 0 || _dataSource == nil) { + return; + } + + // Items + [map insertEmptySectionsOfItemsAtIndexes:sectionIndexes]; + [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind forSections:sectionIndexes owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + + // Supplementaries + for (NSString *kind in [self supplementaryKindsInSections:sectionIndexes]) { + // Step 2: Populate new elements for all sections + [self _insertElementsIntoMap:map kind:kind forSections:sectionIndexes owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + } +} + +#pragma mark - Relayout + +- (void)relayoutNodes:(id)nodes nodesSizeChanged:(NSMutableArray *)nodesSizesChanged +{ + NSParameterAssert(nodesSizesChanged); + + ASDisplayNodeAssertMainThread(); + if (!_initialReloadDataHasBeenCalled) { + return; + } + + for (ASCellNode *node in nodes) { + ASSizeRange constrainedSize = [self constrainedSizeForElement:node.collectionElement inElementMap:_pendingMap]; + [self _layoutNode:node withConstrainedSize:constrainedSize]; + BOOL matchesSize = [_dataSource dataController:self presentedSizeForElement:node.collectionElement matchesSize:node.frame.size]; + if (! matchesSize) { + [nodesSizesChanged addObject:node]; + } + } +} + +- (void)relayoutAllNodes +{ + ASDisplayNodeAssertMainThread(); + if (!_initialReloadDataHasBeenCalled) { + return; + } + + // Can't relayout right away because _visibleMap may not be up-to-date, + // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _visibleMap + LOG(@"Edit Command - relayoutRows"); + [self _scheduleBlockOnMainSerialQueue:^{ + [self _relayoutAllNodes]; + }]; +} + +- (void)_relayoutAllNodes +{ + ASDisplayNodeAssertMainThread(); + for (ASCollectionElement *element in _visibleMap) { + ASSizeRange constrainedSize = [self constrainedSizeForElement:element inElementMap:_visibleMap]; + if (ASSizeRangeHasSignificantArea(constrainedSize)) { + element.constrainedSize = constrainedSize; + + // Node may not be allocated yet (e.g node virtualization or same size optimization) + // Call context.nodeIfAllocated here to avoid immature node allocation and layout + ASCellNode *node = element.nodeIfAllocated; + if (node) { + [self _layoutNode:node withConstrainedSize:constrainedSize]; + } + } + } +} + +# pragma mark - ASPrimitiveTraitCollection + +- (void)environmentDidChange +{ + ASPerformBlockOnMainThread(^{ + if (!_initialReloadDataHasBeenCalled) { + return; + } + + // Can't update the trait collection right away because _visibleMap may not be up-to-date, + // i.e there might be some elements that were allocated using the old trait collection but haven't been added to _visibleMap + [self _scheduleBlockOnMainSerialQueue:^{ + ASPrimitiveTraitCollection newTraitCollection = [[_environmentDelegate dataControllerEnvironment] primitiveTraitCollection]; + for (ASCollectionElement *element in _visibleMap) { + element.traitCollection = newTraitCollection; + } + }]; + }); +} + +# pragma mark - Helper methods + +- (void)_scheduleBlockOnMainSerialQueue:(dispatch_block_t)block +{ + ASDisplayNodeAssertMainThread(); + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + [_mainSerialQueue performBlockOnMainThread:block]; +} + +@end + +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK + +static volatile int64_t _totalExpectedItems = 0; +static volatile int64_t _totalMeasuredNodes = 0; + +@implementation ASDataController (WorkMeasuring) + ++ (void)_didLayoutNode +{ + int64_t measured = OSAtomicIncrement64(&_totalMeasuredNodes); + int64_t expected = _totalExpectedItems; + if (measured % 20 == 0 || measured == expected) { + NSLog(@"Data controller avoided work (underestimated): %lld / %lld", measured, expected); + } +} + ++ (void)_expectToInsertNodes:(NSUInteger)count +{ + OSAtomicAdd64((int64_t)count, &_totalExpectedItems); +} + +@end +#endif diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.h b/Source/Details/ASDelegateProxy.h similarity index 100% rename from AsyncDisplayKit/Details/ASDelegateProxy.h rename to Source/Details/ASDelegateProxy.h diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/Source/Details/ASDelegateProxy.m similarity index 94% rename from AsyncDisplayKit/Details/ASDelegateProxy.m rename to Source/Details/ASDelegateProxy.m index 38c32d2f49..94f113a72d 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/Source/Details/ASDelegateProxy.m @@ -8,10 +8,10 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDelegateProxy.h" -#import "ASTableNode.h" -#import "ASCollectionNode.h" -#import "ASAssert.h" +#import +#import +#import +#import @implementation ASTableViewProxy @@ -54,7 +54,8 @@ - (BOOL)interceptsSelector:(SEL)selector selector == @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:) || // used for batch fetching API - selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) + selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) || + selector == @selector(scrollViewDidEndDecelerating:) ); } @@ -68,6 +69,8 @@ - (BOOL)interceptsSelector:(SEL)selector // handled by ASCollectionView node<->cell machinery selector == @selector(collectionView:cellForItemAtIndexPath:) || selector == @selector(collectionView:layout:sizeForItemAtIndexPath:) || + selector == @selector(collectionView:layout:referenceSizeForHeaderInSection:) || + selector == @selector(collectionView:layout:referenceSizeForFooterInSection:) || selector == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) || // Selection, highlighting, menu @@ -94,6 +97,7 @@ - (BOOL)interceptsSelector:(SEL)selector // used for batch fetching API selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) || + selector == @selector(scrollViewDidEndDecelerating:) || // used for ASCellNode visibility selector == @selector(scrollViewDidScroll:) || diff --git a/Source/Details/ASElementMap.h b/Source/Details/ASElementMap.h new file mode 100644 index 0000000000..17e6fb4f51 --- /dev/null +++ b/Source/Details/ASElementMap.h @@ -0,0 +1,112 @@ +// +// ASElementMap.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/22/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class ASCollectionElement, ASSection, UICollectionViewLayoutAttributes; +@protocol ASSectionContext; + +/** + * An immutable representation of the state of a collection view's data. + * All items and supplementary elements are represented by ASCollectionElement. + * Fast enumeration is in terms of ASCollectionElement. + */ +AS_SUBCLASSING_RESTRICTED +@interface ASElementMap : NSObject + +/** + * The number of sections (of items) in this map. + */ +@property (readonly) NSInteger numberOfSections; + +/** + * The kinds of supplementary elements present in this map. O(1) + */ +@property (copy, readonly) NSArray *supplementaryElementKinds; + +/** + * Returns number of items in the given section. O(1) + */ +- (NSInteger)numberOfItemsInSection:(NSInteger)section; + +/** + * Returns the context object for the given section, if any. O(1) + */ +- (nullable id)contextForSection:(NSInteger)section; + +/** + * All the index paths for all the items in this map. O(N) + * + * This property may be removed in the future, since it doesn't account for supplementary nodes. + */ +@property (copy, readonly) NSArray *itemIndexPaths; + +/** + * All the item elements in this map, in ascending order. O(N) + */ +@property (copy, readonly) NSArray *itemElements; + +/** + * Returns the index path that corresponds to the same element in @c map at the given @c indexPath. O(1) + */ +- (nullable NSIndexPath *)convertIndexPath:(NSIndexPath *)indexPath fromMap:(ASElementMap *)map; + +/** + * Returns the index path for the given element. O(1) + */ +- (nullable NSIndexPath *)indexPathForElement:(ASCollectionElement *)element; + +/** + * Returns the index path for the given element, if it represents a cell. O(1) + */ +- (nullable NSIndexPath *)indexPathForElementIfCell:(ASCollectionElement *)element; + +/** + * Returns the item-element at the given index path. O(1) + */ +- (nullable ASCollectionElement *)elementForItemAtIndexPath:(NSIndexPath *)indexPath; + +/** + * Returns the element for the supplementary element of the given kind at the given index path. O(1) + */ +- (nullable ASCollectionElement *)supplementaryElementOfKind:(NSString *)supplementaryElementKind atIndexPath:(NSIndexPath *)indexPath; + +/** + * Returns the element that corresponds to the given layout attributes, if any. + * + * NOTE: This method only regards the category, kind, and index path of the attributes object. Elements do not + * have any concept of size/position. + */ +- (nullable ASCollectionElement *)elementForLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes; + +#pragma mark - Initialization -- Only Useful to ASDataController + + +// SectionIndex -> ItemIndex -> Element +typedef NSArray *> ASCollectionElementTwoDimensionalArray; + +// ElementKind -> IndexPath -> Element +typedef NSDictionary *> ASSupplementaryElementDictionary; + +/** + * Create a new element map for this dataset. You probably don't need to use this – ASDataController is the only one who creates these. + * + * @param sections The array of ASSection objects. + * @param items A 2D array of ASCollectionElements, for each item. + * @param supplementaryElements A dictionary of gathered supplementary elements. + */ +- (instancetype)initWithSections:(NSArray *)sections + items:(ASCollectionElementTwoDimensionalArray *)items + supplementaryElements:(ASSupplementaryElementDictionary *)supplementaryElements; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASElementMap.m b/Source/Details/ASElementMap.m new file mode 100644 index 0000000000..ee7842ec47 --- /dev/null +++ b/Source/Details/ASElementMap.m @@ -0,0 +1,239 @@ +// +// ASElementMap.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/22/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "ASElementMap.h" +#import +#import +#import +#import +#import +#import +#import + +@interface ASElementMap () + +@property (nonatomic, strong, readonly) NSArray *sections; + +// Element -> IndexPath +@property (nonatomic, strong, readonly) NSMapTable *elementToIndexPathMap; + +// The items, in a 2D array +@property (nonatomic, strong, readonly) ASCollectionElementTwoDimensionalArray *sectionsOfItems; + +@property (nonatomic, strong, readonly) ASSupplementaryElementDictionary *supplementaryElements; + +@end + +@implementation ASElementMap + +- (instancetype)init +{ + return [self initWithSections:@[] items:@[] supplementaryElements:@{}]; +} + +- (instancetype)initWithSections:(NSArray *)sections items:(ASCollectionElementTwoDimensionalArray *)items supplementaryElements:(ASSupplementaryElementDictionary *)supplementaryElements +{ + if (self = [super init]) { + _sections = [sections copy]; + _sectionsOfItems = [[NSArray alloc] initWithArray:items copyItems:YES]; + _supplementaryElements = [[NSDictionary alloc] initWithDictionary:supplementaryElements copyItems:YES]; + + // Setup our index path map + _elementToIndexPathMap = [NSMapTable mapTableWithKeyOptions:(NSMapTableStrongMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableCopyIn]; + NSInteger s = 0; + for (NSArray *section in _sectionsOfItems) { + NSInteger i = 0; + for (ASCollectionElement *element in section) { + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:s]; + [_elementToIndexPathMap setObject:indexPath forKey:element]; + i++; + } + s++; + } + for (NSDictionary *supplementariesForKind in [_supplementaryElements objectEnumerator]) { + [supplementariesForKind enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *_Nonnull indexPath, ASCollectionElement * _Nonnull element, BOOL * _Nonnull stop) { + [_elementToIndexPathMap setObject:indexPath forKey:element]; + }]; + } + } + return self; +} + +- (NSArray *)itemIndexPaths +{ + return ASIndexPathsForTwoDimensionalArray(_sectionsOfItems); +} + +- (NSArray *)itemElements +{ + return ASElementsInTwoDimensionalArray(_sectionsOfItems); +} + +- (NSInteger)numberOfSections +{ + return _sectionsOfItems.count; +} + +- (NSArray *)supplementaryElementKinds +{ + return _supplementaryElements.allKeys; +} + +- (NSInteger)numberOfItemsInSection:(NSInteger)section +{ + if (![self sectionIndexIsValid:section assert:YES]) { + return 0; + } + + return _sectionsOfItems[section].count; +} + +- (id)contextForSection:(NSInteger)section +{ + if (![self sectionIndexIsValid:section assert:NO]) { + return nil; + } + + return _sections[section].context; +} + +- (nullable NSIndexPath *)indexPathForElement:(ASCollectionElement *)element +{ + return [_elementToIndexPathMap objectForKey:element]; +} + +- (nullable NSIndexPath *)indexPathForElementIfCell:(ASCollectionElement *)element +{ + if (element.supplementaryElementKind == nil) { + return [self indexPathForElement:element]; + } else { + return nil; + } +} + +- (nullable ASCollectionElement *)elementForItemAtIndexPath:(NSIndexPath *)indexPath +{ + NSInteger section, item; + if (![self itemIndexPathIsValid:indexPath assert:NO item:&item section:§ion]) { + return nil; + } + + return _sectionsOfItems[section][item]; +} + +- (nullable ASCollectionElement *)supplementaryElementOfKind:(NSString *)supplementaryElementKind atIndexPath:(NSIndexPath *)indexPath +{ + return _supplementaryElements[supplementaryElementKind][indexPath]; +} + +- (ASCollectionElement *)elementForLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes +{ + switch (layoutAttributes.representedElementCategory) { + case UICollectionElementCategoryCell: + // Cell + return [self elementForItemAtIndexPath:layoutAttributes.indexPath]; + case UICollectionElementCategorySupplementaryView: + // Supplementary element. + return [self supplementaryElementOfKind:layoutAttributes.representedElementKind atIndexPath:layoutAttributes.indexPath]; + case UICollectionElementCategoryDecorationView: + // No support for decoration views. + return nil; + } +} + +- (NSIndexPath *)convertIndexPath:(NSIndexPath *)indexPath fromMap:(ASElementMap *)map +{ + id element = [map elementForItemAtIndexPath:indexPath]; + return [self indexPathForElement:element]; +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + return self; +} + +// NSMutableCopying conformance is declared in ASMutableElementMap.h, so that most consumers of ASElementMap don't bother with it. +#pragma mark - NSMutableCopying + +- (id)mutableCopyWithZone:(NSZone *)zone +{ + return [[ASMutableElementMap alloc] initWithSections:_sections items:_sectionsOfItems supplementaryElements:_supplementaryElements]; +} + +#pragma mark - NSFastEnumeration + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id _Nullable __unsafe_unretained [])buffer count:(NSUInteger)len +{ + return [_elementToIndexPathMap countByEnumeratingWithState:state objects:buffer count:len]; +} + +#pragma mark - ASDescriptionProvider + +- (NSString *)description +{ + return ASObjectDescriptionMake(self, [self propertiesForDescription]); +} + +- (NSMutableArray *)propertiesForDescription +{ + NSMutableArray *result = [NSMutableArray array]; + [result addObject:@{ @"items" : _sectionsOfItems }]; + [result addObject:@{ @"supplementaryElements" : _supplementaryElements }]; + return result; +} + +#pragma mark - Internal + +/** + * Fails assert + return NO if section is out of bounds. + */ +- (BOOL)sectionIndexIsValid:(NSInteger)section assert:(BOOL)assert +{ + NSInteger sectionCount = _sectionsOfItems.count; + if (section >= sectionCount) { + if (assert) { + ASDisplayNodeFailAssert(@"Invalid section index %zd when there are only %zd sections!", section, sectionCount); + } + return NO; + } else { + return YES; + } +} + +/** + * If indexPath is nil, just returns NO. + * If indexPath is invalid, fails assertion and returns NO. + * Otherwise returns YES and sets the item & section. + */ +- (BOOL)itemIndexPathIsValid:(NSIndexPath *)indexPath assert:(BOOL)assert item:(out NSInteger *)outItem section:(out NSInteger *)outSection +{ + if (indexPath == nil) { + return NO; + } + + NSInteger section = indexPath.section; + if (![self sectionIndexIsValid:section assert:assert]) { + return NO; + } + + NSInteger itemCount = _sectionsOfItems[section].count; + NSInteger item = indexPath.item; + if (item >= itemCount) { + if (assert) { + ASDisplayNodeFailAssert(@"Invalid item index %zd in section %zd which only has %zd items!", item, section, itemCount); + } + return NO; + } + *outItem = item; + *outSection = section; + return YES; +} + +@end diff --git a/AsyncDisplayKit/Details/ASEventLog.h b/Source/Details/ASEventLog.h similarity index 87% rename from AsyncDisplayKit/Details/ASEventLog.h rename to Source/Details/ASEventLog.h index 1e10193c62..38d122dd06 100644 --- a/AsyncDisplayKit/Details/ASEventLog.h +++ b/Source/Details/ASEventLog.h @@ -10,16 +10,20 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import +#import + #ifndef ASEVENTLOG_CAPACITY #define ASEVENTLOG_CAPACITY 5 #endif #ifndef ASEVENTLOG_ENABLE -#define ASEVENTLOG_ENABLE DEBUG +#define ASEVENTLOG_ENABLE 0 #endif NS_ASSUME_NONNULL_BEGIN +AS_SUBCLASSING_RESTRICTED @interface ASEventLog : NSObject /** diff --git a/AsyncDisplayKit/Details/ASEventLog.mm b/Source/Details/ASEventLog.mm similarity index 95% rename from AsyncDisplayKit/Details/ASEventLog.mm rename to Source/Details/ASEventLog.mm index 1cd5264680..dc9f4da824 100644 --- a/AsyncDisplayKit/Details/ASEventLog.mm +++ b/Source/Details/ASEventLog.mm @@ -10,10 +10,10 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASEventLog.h" -#import "ASThread.h" -#import "ASTraceEvent.h" -#import "ASObjectDescriptionHelpers.h" +#import +#import +#import +#import @implementation ASEventLog { ASDN::RecursiveMutex __instanceLock__; diff --git a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.h b/Source/Details/ASHighlightOverlayLayer.h similarity index 94% rename from AsyncDisplayKit/Details/ASHighlightOverlayLayer.h rename to Source/Details/ASHighlightOverlayLayer.h index 34d31979ec..20d0b01e11 100644 --- a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.h +++ b/Source/Details/ASHighlightOverlayLayer.h @@ -9,9 +9,12 @@ // #import +#import +#import NS_ASSUME_NONNULL_BEGIN +AS_SUBCLASSING_RESTRICTED @interface ASHighlightOverlayLayer : CALayer /** diff --git a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm b/Source/Details/ASHighlightOverlayLayer.mm similarity index 97% rename from AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm rename to Source/Details/ASHighlightOverlayLayer.mm index 0d4032a318..669d6dd144 100644 --- a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm +++ b/Source/Details/ASHighlightOverlayLayer.mm @@ -8,12 +8,12 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASHighlightOverlayLayer.h" +#import #import #import -#import "ASInternalHelpers.h" +#import static const CGFloat kCornerRadius = 2.5; static const UIEdgeInsets padding = {2, 4, 1.5, 4}; diff --git a/AsyncDisplayKit/Details/ASImageContainerProtocolCategories.h b/Source/Details/ASImageContainerProtocolCategories.h similarity index 89% rename from AsyncDisplayKit/Details/ASImageContainerProtocolCategories.h rename to Source/Details/ASImageContainerProtocolCategories.h index f7a433faae..7c5813dc85 100644 --- a/AsyncDisplayKit/Details/ASImageContainerProtocolCategories.h +++ b/Source/Details/ASImageContainerProtocolCategories.h @@ -10,9 +10,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import - -#import "ASImageProtocols.h" +#import +#import @interface UIImage (ASImageContainerProtocol) diff --git a/AsyncDisplayKit/Details/ASImageContainerProtocolCategories.m b/Source/Details/ASImageContainerProtocolCategories.m similarity index 92% rename from AsyncDisplayKit/Details/ASImageContainerProtocolCategories.m rename to Source/Details/ASImageContainerProtocolCategories.m index 4c006a3636..903164ee6f 100644 --- a/AsyncDisplayKit/Details/ASImageContainerProtocolCategories.m +++ b/Source/Details/ASImageContainerProtocolCategories.m @@ -10,7 +10,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASImageContainerProtocolCategories.h" +#import @implementation UIImage (ASImageContainerProtocol) diff --git a/AsyncDisplayKit/Details/ASImageProtocols.h b/Source/Details/ASImageProtocols.h similarity index 86% rename from AsyncDisplayKit/Details/ASImageProtocols.h rename to Source/Details/ASImageProtocols.h index 721f8faeea..435cf0a221 100644 --- a/AsyncDisplayKit/Details/ASImageProtocols.h +++ b/Source/Details/ASImageProtocols.h @@ -9,7 +9,6 @@ // #import -#import NS_ASSUME_NONNULL_BEGIN @@ -26,27 +25,11 @@ typedef void(^ASImageCacherCompletion)(id _Nullable i @protocol ASImageCacheProtocol -@optional - -/** - @abstract Attempts to fetch an image with the given URL from a memory cache. - @param URL The URL of the image to retrieve from the cache. - @discussion This method exists to support synchronous rendering of nodes. Before the layer is drawn, this method - is called to attempt to get the image out of the cache synchronously. This allows drawing to occur on the main thread - if displaysAsynchronously is set to NO or recursivelyEnsureDisplaySynchronously: has been called. - - If `URL` is nil, `completion` will be invoked immediately with a nil image. This method *should* block - the calling thread to fetch the image from a fast memory cache. It is OK to return nil from this method and instead - support only cachedImageWithURL:callbackQueue:completion: however, synchronous rendering will not be possible. - */ -- (nullable id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL; - /** @abstract Attempts to fetch an image with the given URL from the cache. @param URL The URL of the image to retrieve from the cache. @param callbackQueue The queue to call `completion` on. @param completion The block to be called when the cache has either hit or missed. - @param imageFromCache The image that was retrieved from the cache, if the image could be retrieved; nil otherwise. @discussion If `URL` is nil, `completion` will be invoked immediately with a nil image. This method should not block the calling thread as it is likely to be called from the main thread. */ @@ -54,8 +37,23 @@ typedef void(^ASImageCacherCompletion)(id _Nullable i callbackQueue:(dispatch_queue_t)callbackQueue completion:(ASImageCacherCompletion)completion; +@optional + +/** + @abstract Attempts to fetch an image with the given URL from a memory cache. + @param URL The URL of the image to retrieve from the cache. + @discussion This method exists to support synchronous rendering of nodes. Before the layer is drawn, this method + is called to attempt to get the image out of the cache synchronously. This allows drawing to occur on the main thread + if displaysAsynchronously is set to NO or recursivelyEnsureDisplaySynchronously: has been called. + + This method *should* block the calling thread to fetch the image from a fast memory cache. It is OK to return nil from + this method and instead support only cachedImageWithURL:callbackQueue:completion: however, synchronous rendering will + not be possible. + */ +- (nullable id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL; + /** - @abstract Called during clearFetchedData. Allows the cache to optionally trim items. + @abstract Called during clearPreloadedData. Allows the cache to optionally trim items. @note Depending on your caches implementation you may *not* wish to respond to this method. It is however useful if you have a memory and disk cache in which case you'll likely want to clear out the memory cache. */ @@ -86,6 +84,21 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { @required +/** + @abstract Downloads an image with the given URL. + @param URL The URL of the image to download. + @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. + @param downloadProgress The block to be invoked when the download of `URL` progresses. + @param completion The block to be invoked when the download has completed, or has failed. + @discussion This method is likely to be called on the main thread, so any custom implementations should make sure to background any expensive download operations. + @result An opaque identifier to be used in canceling the download, via `cancelImageDownloadForIdentifier:`. You must + retain the identifier if you wish to use it later. + */ +- (nullable id)downloadImageWithURL:(NSURL *)URL + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress + completion:(ASImageDownloaderCompletion)completion; + /** @abstract Cancels an image download. @param downloadIdentifier The opaque download identifier object returned from @@ -97,27 +110,21 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { @optional /** - @abstract Return an object that conforms to ASAnimatedImageProtocol - @param animatedImageData Data that represents an animated image. + @abstract Cancels an image download, however indicating resume data should be stored in case of redownload. + @param downloadIdentifier The opaque download identifier object returned from + `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. + @discussion This method has no effect if `downloadIdentifier` is nil. If implemented, this method + may be called instead of `cancelImageDownloadForIdentifier:` in cases where ASDK believes there's a chance + the image download will be resumed (currently when an image exits preload range). You can use this to store + any data that has already been downloaded for use in resuming the download later. */ -- (nullable id )animatedImageWithData:(NSData *)animatedImageData; - -//You must implement the following method OR the deprecated method at the bottom +- (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier; /** - @abstract Downloads an image with the given URL. - @param URL The URL of the image to download. - @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. - @param downloadProgress The block to be invoked when the download of `URL` progresses. - @param completion The block to be invoked when the download has completed, or has failed. - @discussion This method is likely to be called on the main thread, so any custom implementations should make sure to background any expensive download operations. - @result An opaque identifier to be used in canceling the download, via `cancelImageDownloadForIdentifier:`. You must - retain the identifier if you wish to use it later. + @abstract Return an object that conforms to ASAnimatedImageProtocol + @param animatedImageData Data that represents an animated image. */ -- (nullable id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion; +- (nullable id )animatedImageWithData:(NSData *)animatedImageData; /** @@ -147,8 +154,7 @@ withDownloadIdentifier:(id)downloadIdentifier; @optional /** - @abstract Should be called when the objects cover image is ready. - @param coverImageReadyCallback a block which receives the cover image. + @abstract A block which receives the cover image. Should be called when the objects cover image is ready. */ @property (nonatomic, strong, readwrite) void (^coverImageReadyCallback)(UIImage *coverImage); diff --git a/AsyncDisplayKit/Details/ASLayoutController.h b/Source/Details/ASLayoutController.h similarity index 52% rename from AsyncDisplayKit/Details/ASLayoutController.h rename to Source/Details/ASLayoutController.h index d159cc0384..36017b6233 100644 --- a/AsyncDisplayKit/Details/ASLayoutController.h +++ b/Source/Details/ASLayoutController.h @@ -16,16 +16,17 @@ NS_ASSUME_NONNULL_BEGIN -@class ASCellNode; +@class ASCollectionElement, ASElementMap; -typedef struct { - CGFloat leadingBufferScreenfuls; - CGFloat trailingBufferScreenfuls; -} ASRangeTuningParameters; +ASDISPLAYNODE_EXTERN_C_BEGIN -FOUNDATION_EXPORT ASRangeTuningParameters const ASRangeTuningParametersZero; +struct ASDirectionalScreenfulBuffer { + CGFloat positiveDirection; // Positive relative to iOS Core Animation layer coordinate space. + CGFloat negativeDirection; +}; +typedef struct ASDirectionalScreenfulBuffer ASDirectionalScreenfulBuffer; -FOUNDATION_EXPORT BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningParameters lhs, ASRangeTuningParameters rhs); +ASDISPLAYNODE_EXTERN_C_END @protocol ASLayoutController @@ -33,14 +34,11 @@ FOUNDATION_EXPORT BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRan - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; +- (NSSet *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map; -@optional - -- (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths; +- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet * _Nullable * _Nullable)displaySet preloadSet:(NSSet * _Nullable * _Nullable)preloadSet map:(ASElementMap *)map; -- (void)setViewportSize:(CGSize)viewportSize; -- (CGSize)viewportSize; +@optional @end diff --git a/AsyncDisplayKit/Details/ASLayoutRangeType.h b/Source/Details/ASLayoutRangeType.h similarity index 70% rename from AsyncDisplayKit/Details/ASLayoutRangeType.h rename to Source/Details/ASLayoutRangeType.h index d978da4580..3e5556437d 100644 --- a/AsyncDisplayKit/Details/ASLayoutRangeType.h +++ b/Source/Details/ASLayoutRangeType.h @@ -9,13 +9,25 @@ // #import +#import + +typedef struct { + CGFloat leadingBufferScreenfuls; + CGFloat trailingBufferScreenfuls; +} ASRangeTuningParameters; + +FOUNDATION_EXPORT ASRangeTuningParameters const ASRangeTuningParametersZero; + +FOUNDATION_EXPORT BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningParameters lhs, ASRangeTuningParameters rhs); /** * Each mode has a complete set of tuning parameters for range types. * Depending on some conditions (including interface state and direction of the scroll view, state of rendering engine, etc), * a range controller can choose which mode it should use at a given time. */ -typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) { +typedef NS_ENUM(NSInteger, ASLayoutRangeMode) { + ASLayoutRangeModeUnspecified = -1, + /** * Minimum mode is used when a range controller should limit the amount of work it performs. * Thus, fewer views/layers are created and less data is fetched, saving system resources. @@ -31,8 +43,8 @@ typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) { ASLayoutRangeModeFull, /** - * Visible Only mode is used when a range controller should set its display and fetch data regions to only the size of their bounds. - * This causes all additional backing stores & fetched data to be released, while ensuring a user revisiting the view will + * Visible Only mode is used when a range controller should set its display and preload regions to only the size of their bounds. + * This causes all additional backing stores & preloaded data to be released, while ensuring a user revisiting the view will * still be able to see the expected content. This mode is automatically set on all ASRangeControllers when the app suspends, * allowing the operating system to keep the app alive longer and increase the chance it is still warm when the user returns. */ @@ -40,21 +52,21 @@ typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) { /** * Low Memory mode is used when a range controller should discard ALL graphics buffers, including for the area that would be visible - * the next time the user views it (bounds). The only range it preserves is Fetch Data, which is limited to the bounds, allowing + * the next time the user views it (bounds). The only range it preserves is Preload, which is limited to the bounds, allowing * the content to be restored relatively quickly by re-decoding images (the compressed images are ~10% the size of the decoded ones, * and text is a tiny fraction of its rendered size). */ - ASLayoutRangeModeLowMemory, - ASLayoutRangeModeCount + ASLayoutRangeModeLowMemory }; -#define ASLayoutRangeModeInvalid ASLayoutRangeModeCount +static NSInteger const ASLayoutRangeModeCount = 4; typedef NS_ENUM(NSInteger, ASLayoutRangeType) { ASLayoutRangeTypeDisplay, - ASLayoutRangeTypePreload, - ASLayoutRangeTypeCount + ASLayoutRangeTypePreload }; +static NSInteger const ASLayoutRangeTypeCount = 2; + #define ASLayoutRangeTypeRender ASLayoutRangeTypeDisplay -#define ASLayoutRangeTypePreload ASLayoutRangeTypePreload +#define ASLayoutRangeTypeFetchData ASLayoutRangeTypePreload diff --git a/AsyncDisplayKit/Details/ASMainSerialQueue.h b/Source/Details/ASMainSerialQueue.h similarity index 88% rename from AsyncDisplayKit/Details/ASMainSerialQueue.h rename to Source/Details/ASMainSerialQueue.h index 30cf29bbaa..0dc96d52d1 100644 --- a/AsyncDisplayKit/Details/ASMainSerialQueue.h +++ b/Source/Details/ASMainSerialQueue.h @@ -11,7 +11,9 @@ // #import +#import +AS_SUBCLASSING_RESTRICTED @interface ASMainSerialQueue : NSObject - (void)performBlockOnMainThread:(dispatch_block_t)block; diff --git a/AsyncDisplayKit/Details/ASMainSerialQueue.mm b/Source/Details/ASMainSerialQueue.mm similarity index 91% rename from AsyncDisplayKit/Details/ASMainSerialQueue.mm rename to Source/Details/ASMainSerialQueue.mm index e6ed595450..ec6522bede 100644 --- a/AsyncDisplayKit/Details/ASMainSerialQueue.mm +++ b/Source/Details/ASMainSerialQueue.mm @@ -10,10 +10,10 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASMainSerialQueue.h" +#import -#import "ASThread.h" -#import "ASInternalHelpers.h" +#import +#import @interface ASMainSerialQueue () { diff --git a/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h b/Source/Details/ASMutableAttributedStringBuilder.h similarity index 97% rename from AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h rename to Source/Details/ASMutableAttributedStringBuilder.h index af00fe7195..3ee9d88215 100644 --- a/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h +++ b/Source/Details/ASMutableAttributedStringBuilder.h @@ -9,6 +9,7 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN @@ -39,6 +40,7 @@ NS_ASSUME_NONNULL_BEGIN * * Please note that ALL of the standard NSString methods are left unimplemented. */ +AS_SUBCLASSING_RESTRICTED @interface ASMutableAttributedStringBuilder : NSMutableAttributedString - (instancetype)initWithString:(NSString *)str attributes:(nullable NSDictionary *)attrs; diff --git a/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.m b/Source/Details/ASMutableAttributedStringBuilder.m similarity index 99% rename from AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.m rename to Source/Details/ASMutableAttributedStringBuilder.m index 87072c36a6..5b0d40c4a1 100644 --- a/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.m +++ b/Source/Details/ASMutableAttributedStringBuilder.m @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASMutableAttributedStringBuilder.h" +#import @implementation ASMutableAttributedStringBuilder { // Flag for the type of the current transaction (set or add) diff --git a/AsyncDisplayKit/Details/ASObjectDescriptionHelpers.h b/Source/Details/ASObjectDescriptionHelpers.h similarity index 87% rename from AsyncDisplayKit/Details/ASObjectDescriptionHelpers.h rename to Source/Details/ASObjectDescriptionHelpers.h index c969a2682f..e829f79af3 100644 --- a/AsyncDisplayKit/Details/ASObjectDescriptionHelpers.h +++ b/Source/Details/ASObjectDescriptionHelpers.h @@ -8,10 +8,19 @@ #import #import -#import NS_ASSUME_NONNULL_BEGIN +@protocol ASDebugNameProvider + +@required +/** + * @abstract Name that is printed by ascii art string and displayed in description. + */ +@property (nullable, nonatomic, copy) NSString *debugName; + +@end + /** * Your base class should conform to this and override `-debugDescription` * to call `[self propertiesForDebugDescription]` and use `ASObjectDescriptionMake` @@ -50,7 +59,7 @@ NSString *ASObjectDescriptionMake(__autoreleasing id object, NSArray -#import "NSIndexSet+ASHelpers.h" +#import + +#import + +#import NSString *ASGetDescriptionValueString(id object) { @@ -16,6 +18,7 @@ // Use shortened NSValue descriptions NSValue *value = object; const char *type = value.objCType; + if (strcmp(type, @encode(CGRect)) == 0) { CGRect rect = [value CGRectValue]; return [NSString stringWithFormat:@"(%g %g; %g %g)", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height]; @@ -24,6 +27,7 @@ } else if (strcmp(type, @encode(CGPoint)) == 0) { return NSStringFromCGPoint(value.CGPointValue); } + } else if ([object isKindOfClass:[NSIndexSet class]]) { return [object as_smallDescription]; } else if ([object isKindOfClass:[NSIndexPath class]]) { diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h b/Source/Details/ASPINRemoteImageDownloader.h similarity index 93% rename from AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h rename to Source/Details/ASPINRemoteImageDownloader.h index a2833cde90..268a5ff4e9 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h +++ b/Source/Details/ASPINRemoteImageDownloader.h @@ -10,14 +10,16 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#if PIN_REMOTE_IMAGE +#import -#import -#import "ASImageProtocols.h" -#import +#if AS_PIN_REMOTE_IMAGE + +#import NS_ASSUME_NONNULL_BEGIN +@class PINRemoteImageManager; + @interface ASPINRemoteImageDownloader : NSObject /** diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/Source/Details/ASPINRemoteImageDownloader.m similarity index 89% rename from AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m rename to Source/Details/ASPINRemoteImageDownloader.m index 1c48cc0824..b861c3055b 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m +++ b/Source/Details/ASPINRemoteImageDownloader.m @@ -10,16 +10,18 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#if PIN_REMOTE_IMAGE -#import "ASPINRemoteImageDownloader.h" +#import -#import "ASAssert.h" -#import "ASThread.h" -#import "ASImageContainerProtocolCategories.h" +#if AS_PIN_REMOTE_IMAGE +#import -#if __has_include ("PINAnimatedImage.h") +#import +#import +#import + +#if __has_include () #define PIN_ANIMATED_AVAILABLE 1 -#import "PINAnimatedImage.h" +#import #import #else #define PIN_ANIMATED_AVAILABLE 0 @@ -128,7 +130,8 @@ + (PINRemoteImageManager *)sharedPINRemoteImageManagerWithConfiguration:(NSURLSe userInfo:nil]; @throw e; } - sharedPINRemoteImageManager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:configuration alternativeRepresentationProvider:self]; + sharedPINRemoteImageManager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:configuration + alternativeRepresentationProvider:[self sharedDownloader]]; #else sharedPINRemoteImageManager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:configuration]; #endif @@ -163,8 +166,8 @@ - (BOOL)sharedImageManagerSupportsMemoryRemoval - (id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL; { PINRemoteImageManager *manager = [self sharedPINRemoteImageManager]; - NSString *key = [manager cacheKeyForURL:URL processorKey:nil]; - PINRemoteImageManagerResult *result = [manager synchronousImageFromCacheWithCacheKey:key options:PINRemoteImageManagerDownloadOptionsSkipDecode]; + PINRemoteImageManagerResult *result = [manager synchronousImageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode]; + #if PIN_ANIMATED_AVAILABLE if (result.alternativeRepresentation) { return result.alternativeRepresentation; @@ -243,12 +246,14 @@ - (nullable id)downloadImageWithURL:(NSURL *)URL - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier { - if (!downloadIdentifier) { - return; - } + ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); + [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:NO]; +} +- (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier +{ ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier]; + [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:YES]; } - (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier @@ -270,18 +275,18 @@ - (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:( { ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityMedium; + PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityDefault; switch (priority) { case ASImageDownloaderPriorityPreload: - pi_priority = PINRemoteImageManagerPriorityMedium; + pi_priority = PINRemoteImageManagerPriorityLow; break; case ASImageDownloaderPriorityImminent: - pi_priority = PINRemoteImageManagerPriorityHigh; + pi_priority = PINRemoteImageManagerPriorityDefault; break; case ASImageDownloaderPriorityVisible: - pi_priority = PINRemoteImageManagerPriorityVeryHigh; + pi_priority = PINRemoteImageManagerPriorityHigh; break; } [[self sharedPINRemoteImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier]; diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h b/Source/Details/ASPhotosFrameworkImageRequest.h similarity index 86% rename from AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h rename to Source/Details/ASPhotosFrameworkImageRequest.h index 72b45ea561..83d5c9cc38 100644 --- a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h +++ b/Source/Details/ASPhotosFrameworkImageRequest.h @@ -9,11 +9,11 @@ // LICENSE file in the root directory of this source tree. An additional grant // of patent rights can be found in the PATENTS file in the same directory. // -#if TARGET_OS_IOS -#import + +#import #import -// NS_ASSUME_NONNULL_BEGIN +NS_ASSUME_NONNULL_BEGIN extern NSString *const ASPhotosURLScheme; @@ -28,7 +28,7 @@ extern NSString *const ASPhotosURLScheme; /** @return A new image request deserialized from `url`, or nil if `url` is not a valid photos URL. */ -+ (/*nullable*/ ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url; ++ (nullable ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url; /** @abstract The asset identifier for this image request provided during initialization. @@ -60,12 +60,6 @@ extern NSString *const ASPhotosURLScheme; */ @property (nonatomic, readonly) NSURL *url; -/** - @return `YES` if `object` is an equivalent image request, `NO` otherwise. - */ -- (BOOL)isEqual:(id)object; - @end -// NS_ASSUME_NONNULL_END -#endif +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m b/Source/Details/ASPhotosFrameworkImageRequest.m similarity index 96% rename from AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m rename to Source/Details/ASPhotosFrameworkImageRequest.m index aac715563c..d51c49a81f 100644 --- a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m +++ b/Source/Details/ASPhotosFrameworkImageRequest.m @@ -9,10 +9,9 @@ // LICENSE file in the root directory of this source tree. An additional grant // of patent rights can be found in the PATENTS file in the same directory. // -#if TARGET_OS_IOS -#import "ASPhotosFrameworkImageRequest.h" -#import "ASBaseDefines.h" -#import "ASAvailability.h" + +#import +#import NSString *const ASPhotosURLScheme = @"ph"; @@ -105,8 +104,8 @@ - (NSURL *)url + (ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url { - // not a photos URL or iOS < 8 - if (![url.scheme isEqualToString:ASPhotosURLScheme] || !AS_AT_LEAST_IOS8) { + // not a photos URL + if (![url.scheme isEqualToString:ASPhotosURLScheme]) { return nil; } @@ -163,4 +162,3 @@ - (BOOL)isEqual:(id)object } @end -#endif diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/Source/Details/ASRangeController.h similarity index 64% rename from AsyncDisplayKit/Details/ASRangeController.h rename to Source/Details/ASRangeController.h index 5d3ebc9484..7c1afd2c0a 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/Source/Details/ASRangeController.h @@ -11,16 +11,19 @@ #import #import #import -#import +#import #import #import +#import #define ASRangeControllerLoggingEnabled 0 NS_ASSUME_NONNULL_BEGIN +@class _ASHierarchyChangeSet; @protocol ASRangeControllerDataSource; @protocol ASRangeControllerDelegate; +@protocol ASLayoutController; /** * Working range controller. @@ -30,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN * "working ranges" to trigger network calls and rendering, and is responsible for driving asynchronous layout of cells. * This includes cancelling those asynchronous operations as cells fall outside of the working ranges. */ +AS_SUBCLASSING_RESTRICTED @interface ASRangeController : NSObject { id _layoutController; @@ -69,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN // These methods call the corresponding method on each node, visiting each one that // the range controller has set a non-default interface state on. - (void)clearContents; -- (void)clearFetchedData; +- (void)clearPreloadedData; /** * An object that describes the layout behavior of the ranged component (table view, collection view, etc.) @@ -103,9 +107,9 @@ NS_ASSUME_NONNULL_BEGIN /** * @param rangeController Sender. * - * @return an array of index paths corresponding to the nodes currently visible onscreen (i.e., the visible range). + * @return an array of elements corresponding to the data currently visible onscreen (i.e., the visible range). */ -- (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController; +- (NSArray *)visibleElementsForRangeController:(ASRangeController *)rangeController; /** * @param rangeController Sender. @@ -114,13 +118,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController; -/** - * @param rangeController Sender. - * - * @return the receiver's viewport size (i.e., the screen space occupied by the visible range). - */ -- (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController; - /** * @param rangeController Sender. * @@ -130,9 +127,7 @@ NS_ASSUME_NONNULL_BEGIN */ - (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController; -- (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath; - -- (NSArray *> *)completedNodes; +- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController; - (NSString *)nameForRangeControllerDataSource; @@ -144,75 +139,18 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASRangeControllerDelegate /** - * Begin updates. + * Called before updating with given change set. * - * @param rangeController Sender. + * @param changeSet The change set that includes all updates */ -- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController; +- (void)rangeController:(ASRangeController *)rangeController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet; /** - * End updates. + * Called after updating with given change set. * - * @param rangeController Sender. - * @param animated NO if all animations are disabled. YES otherwise. - * @param completion Completion block. - */ -- (void)rangeController:(ASRangeController * )rangeController didEndUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion; - -/** - * Completed updates to cell node addition and removal. - * - * @param rangeController Sender. + * @param changeSet The change set that includes all updates */ -- (void)didCompleteUpdatesInRangeController:(ASRangeController *)rangeController; - -/** - * Called for nodes insertion. - * - * @param rangeController Sender. - * - * @param nodes Inserted nodes. - * - * @param indexPaths Index path of inserted nodes. - * - * @param animationOptions Animation options. See ASDataControllerAnimationOptions. - */ -- (void)rangeController:(ASRangeController *)rangeController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - * Called for nodes deletion. - * - * @param rangeController Sender. - * - * @param nodes Deleted nodes. - * - * @param indexPaths Index path of deleted nodes. - * - * @param animationOptions Animation options. See ASDataControllerAnimationOptions. - */ -- (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - * Called for section insertion. - * - * @param rangeController Sender. - * - * @param indexSet Index set of inserted sections. - * - * @param animationOptions Animation options. See ASDataControllerAnimationOptions. - */ -- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - * Called for section deletion. - * - * @param rangeController Sender. - * - * @param indexSet Index set of deleted sections. - * - * @param animationOptions Animation options. See ASDataControllerAnimationOptions. - */ -- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)rangeController:(ASRangeController *)rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet; @end @@ -224,8 +162,8 @@ NS_ASSUME_NONNULL_BEGIN * * Logic for the automatic range mode: * 1. If there are no visible node paths available nothing is to be done and no range update will happen - * 2. The initial range update if the range controller is visible always will be ASLayoutRangeModeCount - * (ASLayoutRangeModeMinimum) as it's the initial fetch + * 2. The initial range update if the range controller is visible always will be + * ASLayoutRangeModeMinimum as it's the initial fetch * 3. The range mode set explicitly via updateCurrentRangeWithMode: will last at least one range update. After that it the range controller will use the explicit set range mode until it becomes visible and a new range update was triggered or a new range mode via updateCurrentRangeWithMode: is set @@ -235,4 +173,20 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface ASRangeController (DebugInternal) + ++ (void)layoutDebugOverlayIfNeeded; + +- (void)addRangeControllerToRangeDebugOverlay; + +- (void)updateRangeController:(ASRangeController *)controller + withScrollableDirections:(ASScrollDirection)scrollableDirections + scrollDirection:(ASScrollDirection)direction + rangeMode:(ASLayoutRangeMode)mode + displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters + preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters + interfaceState:(ASInterfaceState)interfaceState; + +@end + NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/Source/Details/ASRangeController.mm similarity index 66% rename from AsyncDisplayKit/Details/ASRangeController.mm rename to Source/Details/ASRangeController.mm index 90e814dcbe..e5ae2345f4 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/Source/Details/ASRangeController.mm @@ -8,33 +8,44 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASRangeController.h" - -#import "ASAssert.h" -#import "ASCellNode.h" -#import "ASDisplayNodeExtras.h" -#import "ASDisplayNodeInternal.h" -#import "ASMultidimensionalArrayUtils.h" -#import "ASInternalHelpers.h" -#import "ASMultiDimensionalArrayUtils.h" -#import "ASWeakSet.h" - -#import "ASDisplayNode+FrameworkPrivate.h" -#import "AsyncDisplayKit+Debug.h" +#import + +#import +#import +#import +#import +#import +#import // Required for interfaceState and hierarchyState setter methods. +#import +#import +#import +#import + +#import +#import #define AS_RANGECONTROLLER_LOG_UPDATE_FREQ 0 +#ifndef ASRangeControllerAutomaticLowMemoryHandling +#define ASRangeControllerAutomaticLowMemoryHandling 1 +#endif + @interface ASRangeController () { BOOL _rangeIsValid; BOOL _needsRangeUpdate; - BOOL _layoutControllerImplementsSetVisibleIndexPaths; - BOOL _layoutControllerImplementsSetViewportSize; NSSet *_allPreviousIndexPaths; + ASWeakSet *_visibleNodes; ASLayoutRangeMode _currentRangeMode; - BOOL _didUpdateCurrentRange; + BOOL _preserveCurrentRangeMode; BOOL _didRegisterForNodeDisplayNotifications; CFTimeInterval _pendingDisplayNodesTimestamp; + + // If the user is not currently scrolling, we will keep our ranges + // configured to match their previous scroll direction. Defaults + // to [.right, .down] so that when the user first opens a screen + // the ranges point down into the content. + ASScrollDirection _previousScrollDirection; #if AS_RANGECONTROLLER_LOG_UPDATE_FREQ NSUInteger _updateCountThisFrame; @@ -57,8 +68,9 @@ - (instancetype)init } _rangeIsValid = YES; - _currentRangeMode = ASLayoutRangeModeInvalid; - _didUpdateCurrentRange = NO; + _currentRangeMode = ASLayoutRangeModeUnspecified; + _preserveCurrentRangeMode = NO; + _previousScrollDirection = ASScrollDirectionDown | ASScrollDirectionRight; [[[self class] allRangeControllersWeakSet] addObject:self]; @@ -67,7 +79,7 @@ - (instancetype)init [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; #endif - if ([ASRangeController shouldShowRangeDebugOverlay]) { + if (ASDisplayNode.shouldShowRangeDebugOverlay) { [self addRangeControllerToRangeDebugOverlay]; } @@ -89,7 +101,7 @@ - (void)dealloc + (BOOL)isFirstRangeUpdateForRangeMode:(ASLayoutRangeMode)rangeMode { - return (rangeMode == ASLayoutRangeModeInvalid); + return (rangeMode == ASLayoutRangeModeUnspecified); } + (ASLayoutRangeMode)rangeModeForInterfaceState:(ASInterfaceState)interfaceState @@ -140,9 +152,9 @@ - (void)updateIfNeeded - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode { + _preserveCurrentRangeMode = YES; if (_currentRangeMode != rangeMode) { _currentRangeMode = rangeMode; - _didUpdateCurrentRange = YES; [self setNeedsUpdate]; } @@ -151,8 +163,6 @@ - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode - (void)setLayoutController:(id)layoutController { _layoutController = layoutController; - _layoutControllerImplementsSetVisibleIndexPaths = [layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]; - _layoutControllerImplementsSetViewportSize = [layoutController respondsToSelector:@selector(setViewportSize:)]; if (layoutController && _dataSource) { [self updateIfNeeded]; } @@ -166,6 +176,24 @@ - (void)setDataSource:(id)dataSource } } +// Clear the visible bit from any nodes that disappeared since last update. +// Currently we guarantee that nodes will not be marked visible when deallocated, +// but it's OK to be in e.g. the preload range. So for the visible bit specifically, +// we add this extra mechanism to account for e.g. deleted items. +// +// NOTE: There is a minor risk here, if a node is transferred from one range controller +// to another before the first rc updates and clears the node out of this set. It's a pretty +// wild scenario that I doubt happens in practice. +- (void)_setVisibleNodes:(ASWeakSet *)newVisibleNodes +{ + for (ASCellNode *node in _visibleNodes) { + if (![newVisibleNodes containsObject:node] && node.isVisible) { + [node exitInterfaceState:ASInterfaceStateVisible]; + } + } + _visibleNodes = newVisibleNodes; +} + - (void)_updateVisibleNodeIndexPaths { ASDisplayNodeAssert(_layoutController, @"An ASLayoutController is required by ASRangeController"); @@ -177,76 +205,86 @@ - (void)_updateVisibleNodeIndexPaths _updateCountThisFrame += 1; #endif - // allNodes is a 2D array: it contains arrays for each section, each containing nodes. - NSArray *allNodes = [_dataSource completedNodes]; - NSUInteger numberOfSections = [allNodes count]; + ASElementMap *map = [_dataSource elementMapForRangeController:self]; // TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges // Example: ... = [_layoutController indexPathsForScrolling:scrollDirection rangeType:ASLayoutRangeTypeVisible]; - NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self]; - - if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... + NSSet *visibleElements = [NSSet setWithArray:[_dataSource visibleElementsForRangeController:self]]; + ASWeakSet *newVisibleNodes = [[ASWeakSet alloc] init]; + + if (visibleElements.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... + [self _setVisibleNodes:newVisibleNodes]; return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later } ASProfilingSignpostStart(1, self); + // Get the scroll direction. Default to using the previous one, if they're not scrolling. ASScrollDirection scrollDirection = [_dataSource scrollDirectionForRangeController:self]; - if (_layoutControllerImplementsSetViewportSize) { - [_layoutController setViewportSize:[_dataSource viewportSizeForRangeController:self]]; + if (scrollDirection == ASScrollDirectionNone) { + scrollDirection = _previousScrollDirection; } - - // the layout controller needs to know what the current visible indices are to calculate range offsets - if (_layoutControllerImplementsSetVisibleIndexPaths) { - [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; - } - - NSArray *currentSectionNodes = nil; - NSInteger currentSectionIndex = -1; // Set to -1 so we don't match any indexPath.section on the first iteration. - NSUInteger numberOfNodesInSection = 0; - - NSSet *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths]; - NSSet *displayIndexPaths = nil; - NSSet *preloadIndexPaths = nil; - - // Prioritize the order in which we visit each. Visible nodes should be updated first so they are enqueued on - // the network or display queues before preloading (offscreen) nodes are enqueued. - NSMutableOrderedSet *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths]; + _previousScrollDirection = scrollDirection; ASInterfaceState selfInterfaceState = [self interfaceState]; ASLayoutRangeMode rangeMode = _currentRangeMode; // If the range mode is explicitly set via updateCurrentRangeWithMode: it will last in that mode until the // range controller becomes visible again or explicitly changes the range mode again - if ((!_didUpdateCurrentRange && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) { + if ((!_preserveCurrentRangeMode && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) { rangeMode = [ASRangeController rangeModeForInterfaceState:selfInterfaceState currentRangeMode:_currentRangeMode]; } ASRangeTuningParameters parametersPreload = [_layoutController tuningParametersForRangeMode:rangeMode - rangeType:ASLayoutRangeTypePreload]; - if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersPreload, ASRangeTuningParametersZero)) { - preloadIndexPaths = visibleIndexPaths; - } else { - preloadIndexPaths = [_layoutController indexPathsForScrolling:scrollDirection - rangeMode:rangeMode - rangeType:ASLayoutRangeTypePreload]; - } - + rangeType:ASLayoutRangeTypePreload]; ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay]; - if (rangeMode == ASLayoutRangeModeLowMemory) { - displayIndexPaths = [NSSet set]; - } else if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, ASRangeTuningParametersZero)) { - displayIndexPaths = visibleIndexPaths; - } else if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, parametersPreload)) { - displayIndexPaths = preloadIndexPaths; + + // Preload can express the ultra-low-memory state with 0, 0 returned for its tuningParameters above, and will match Visible. + // However, in this rangeMode, Display is not supposed to contain *any* paths -- not even the visible bounds. TuningParameters can't express this. + BOOL emptyDisplayRange = (rangeMode == ASLayoutRangeModeLowMemory); + BOOL equalDisplayPreload = ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, parametersPreload); + BOOL equalDisplayVisible = (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, ASRangeTuningParametersZero) + && emptyDisplayRange == NO); + + // Check if both Display and Preload are unique. If they are, we load them with a single fetch from the layout controller for performance. + BOOL optimizedLoadingOfBothRanges = (equalDisplayPreload == NO && equalDisplayVisible == NO && emptyDisplayRange == NO); + + NSSet *displayElements = nil; + NSSet *preloadElements = nil; + + if (optimizedLoadingOfBothRanges) { + [_layoutController allElementsForScrolling:scrollDirection rangeMode:rangeMode displaySet:&displayElements preloadSet:&preloadElements map:map]; } else { - displayIndexPaths = [_layoutController indexPathsForScrolling:scrollDirection - rangeMode:rangeMode - rangeType:ASLayoutRangeTypeDisplay]; + if (emptyDisplayRange == YES) { + displayElements = [NSSet set]; + } if (equalDisplayVisible == YES) { + displayElements = visibleElements; + } else { + // Calculating only the Display range means the Preload range is either the same as Display or Visible. + displayElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay map:map]; + } + + BOOL equalPreloadVisible = ASRangeTuningParametersEqualToRangeTuningParameters(parametersPreload, ASRangeTuningParametersZero); + if (equalDisplayPreload == YES) { + preloadElements = displayElements; + } else if (equalPreloadVisible == YES) { + preloadElements = visibleElements; + } else { + preloadElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypePreload map:map]; + } } + // For now we are only interested in items. Filter-map out from element to item-index-path. + NSSet *visibleIndexPaths = ASSetByFlatMapping(visibleElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]); + NSSet *displayIndexPaths = ASSetByFlatMapping(displayElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]); + NSSet *preloadIndexPaths = ASSetByFlatMapping(preloadElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]); + + // Prioritize the order in which we visit each. Visible nodes should be updated first so they are enqueued on + // the network or display queues before preloading (offscreen) nodes are enqueued. + NSMutableOrderedSet *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths]; + // Typically the preloadIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. // Because allIndexPaths is an NSMutableOrderedSet, this adds the non-duplicate items /after/ the existing items. - // This means that during iteration, we will first visit visible, then display, then fetch data nodes. + // This means that during iteration, we will first visit visible, then display, then preload nodes. [allIndexPaths unionSet:displayIndexPaths]; [allIndexPaths unionSet:preloadIndexPaths]; @@ -259,17 +297,17 @@ - (void)_updateVisibleNodeIndexPaths _allPreviousIndexPaths = allCurrentIndexPaths; _currentRangeMode = rangeMode; - _didUpdateCurrentRange = NO; + _preserveCurrentRangeMode = NO; if (!_rangeIsValid) { - [allIndexPaths addObjectsFromArray:ASIndexPathsForTwoDimensionalArray(allNodes)]; + [allIndexPaths addObjectsFromArray:map.itemIndexPaths]; } #if ASRangeControllerLoggingEnabled ASDisplayNodeAssertTrue([visibleIndexPaths isSubsetOfSet:displayIndexPaths]); NSMutableArray *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil); #endif - + for (NSIndexPath *indexPath in allIndexPaths) { // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. // For consistency, make sure each node knows that it should measure itself if something changes. @@ -288,14 +326,14 @@ - (void)_updateVisibleNodeIndexPaths } } else { // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the - // instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet. + // instant we come onscreen. So, preload and display all of those things, but don't waste resources preloading yet. // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:. if ([allCurrentIndexPaths containsObject:indexPath]) { // DO NOT set Visible: even though these elements are in the visible range / "viewport", // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above - // Set Layout, Fetch Data + // Set Layout, Preload interfaceState |= ASInterfaceStatePreload; if (rangeMode != ASLayoutRangeModeLowMemory) { @@ -306,45 +344,36 @@ - (void)_updateVisibleNodeIndexPaths } } } - - NSInteger section = indexPath.section; - NSInteger row = indexPath.row; - - if (section >= 0 && row >= 0 && section < numberOfSections) { - if (section != currentSectionIndex) { - // Often we'll be dealing with indexPaths in the same section, but the set isn't sorted and we may even bounce - // between the same ones. Still, this saves dozens of method calls to access the inner array and count. - currentSectionNodes = allNodes[section]; - numberOfNodesInSection = [currentSectionNodes count]; - currentSectionIndex = section; + + ASCellNode *node = [map elementForItemAtIndexPath:indexPath].nodeIfAllocated; + if (node != nil) { + ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); + if (ASInterfaceStateIncludesVisible(interfaceState)) { + [newVisibleNodes addObject:node]; } - - if (row < numberOfNodesInSection) { - ASDisplayNode *node = currentSectionNodes[row]; - - ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); - // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. - if (node.interfaceState != interfaceState) { + // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. + if (node.interfaceState != interfaceState) { #if ASRangeControllerLoggingEnabled - [modifiedIndexPaths addObject:indexPath]; + [modifiedIndexPaths addObject:indexPath]; #endif - - BOOL nodeShouldScheduleDisplay = [node shouldScheduleDisplayWithNewInterfaceState:interfaceState]; - [node recursivelySetInterfaceState:interfaceState]; - - if (nodeShouldScheduleDisplay) { - [self registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:selfInterfaceState]; - if (_didRegisterForNodeDisplayNotifications) { - _pendingDisplayNodesTimestamp = CACurrentMediaTime(); - } + + BOOL nodeShouldScheduleDisplay = [node shouldScheduleDisplayWithNewInterfaceState:interfaceState]; + [node recursivelySetInterfaceState:interfaceState]; + + if (nodeShouldScheduleDisplay) { + [self registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:selfInterfaceState]; + if (_didRegisterForNodeDisplayNotifications) { + _pendingDisplayNodesTimestamp = CACurrentMediaTime(); } } } } } + + [self _setVisibleNodes:newVisibleNodes]; // TODO: This code is for debugging only, but would be great to clean up with a delegate method implementation. - if ([ASRangeController shouldShowRangeDebugOverlay]) { + if (ASDisplayNode.shouldShowRangeDebugOverlay) { ASScrollDirection scrollableDirections = ASScrollDirectionUp | ASScrollDirectionDown; if ([_dataSource isKindOfClass:NSClassFromString(@"ASCollectionView")]) { #pragma clang diagnostic push @@ -375,16 +404,22 @@ - (void)_updateVisibleNodeIndexPaths [modifiedIndexPaths sortUsingSelector:@selector(compare:)]; NSLog(@"Range update complete; modifiedIndexPaths: %@", [self descriptionWithIndexPaths:modifiedIndexPaths]); #endif - [_delegate didCompleteUpdatesInRangeController:self]; ASProfilingSignpostEnd(1, self); } #pragma mark - Notification observers +/** + * If we're in a restricted range mode, but we're going to change to a full range mode soon, + * go ahead and schedule the transition as soon as all the currently-scheduled rendering is done #1163. + */ - (void)registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState { - if (!_didRegisterForNodeDisplayNotifications) { + // Do not schedule to listen if we're already in full range mode. + // This avoids updating the range controller during a collection teardown when it is removed + // from the hierarchy and its data source is cleared, causing UIKit to call -reloadData. + if (!_didRegisterForNodeDisplayNotifications && _currentRangeMode != ASLayoutRangeModeFull) { ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState currentRangeMode:_currentRangeMode]; if (_currentRangeMode != nextRangeMode) { @@ -416,7 +451,13 @@ - (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)nod ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(node, @"Cannot move a nil node to a view"); ASDisplayNodeAssert(contentView, @"Cannot move a node to a non-existent view"); - + + if (node.shouldUseUIKitCell) { + // When using UIKit cells, the ASCellNode is just a placeholder object with a preferredSize. + // In this case, we should not disrupt the subviews of the contentView. + return; + } + if (node.view.superview == contentView) { // this content view is already correctly configured return; @@ -442,46 +483,20 @@ - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)range #pragma mark - ASDataControllerDelegete -- (void)dataControllerBeginUpdates:(ASDataController *)dataController -{ - ASDisplayNodeAssertMainThread(); - [_delegate didBeginUpdatesInRangeController:self]; -} - -- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion +- (void)dataController:(ASDataController *)dataController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet { ASDisplayNodeAssertMainThread(); - [_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion]; -} - -- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); - ASDisplayNodeAssertMainThread(); - _rangeIsValid = NO; - [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; -} - -- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - _rangeIsValid = NO; - [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; -} - -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); - ASDisplayNodeAssertMainThread(); - _rangeIsValid = NO; - [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + if (changeSet.includesReloadData) { + [self _setVisibleNodes:nil]; + } + [_delegate rangeController:self willUpdateWithChangeSet:changeSet]; } -- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)dataController:(ASDataController *)dataController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet { ASDisplayNodeAssertMainThread(); _rangeIsValid = NO; - [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + [_delegate rangeController:self didUpdateWithChangeSet:changeSet]; } #pragma mark - Memory Management @@ -489,22 +504,20 @@ - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtInd // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. - (void)clearContents { - for (NSArray *section in [_dataSource completedNodes]) { - for (ASDisplayNode *node in section) { - if (ASInterfaceStateIncludesDisplay(node.interfaceState)) { - [node exitInterfaceState:ASInterfaceStateDisplay]; - } + for (ASCollectionElement *element in [_dataSource elementMapForRangeController:self]) { + ASCellNode *node = element.nodeIfAllocated; + if (ASInterfaceStateIncludesDisplay(node.interfaceState)) { + [node exitInterfaceState:ASInterfaceStateDisplay]; } } } -- (void)clearFetchedData +- (void)clearPreloadedData { - for (NSArray *section in [_dataSource completedNodes]) { - for (ASDisplayNode *node in section) { - if (ASInterfaceStateIncludesPreload(node.interfaceState)) { - [node exitInterfaceState:ASInterfaceStatePreload]; - } + for (ASCollectionElement *element in [_dataSource elementMapForRangeController:self]) { + ASCellNode *node = element.nodeIfAllocated; + if (ASInterfaceStateIncludesPreload(node.interfaceState)) { + [node exitInterfaceState:ASInterfaceStatePreload]; } } } @@ -532,7 +545,7 @@ + (void)registerSharedApplicationNotifications [center addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; } -static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeVisibleOnly; +static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeLowMemory; + (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode { ASDisplayNodeAssert(rangeMode == ASLayoutRangeModeVisibleOnly || rangeMode == ASLayoutRangeModeLowMemory, @"It is highly inadvisable to engage a larger range mode when a memory warning occurs, as this will almost certainly cause app eviction"); @@ -544,8 +557,8 @@ + (void)didReceiveMemoryWarning:(NSNotification *)notification NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects]; for (ASRangeController *rangeController in allRangeControllers) { BOOL isDisplay = ASInterfaceStateIncludesDisplay([rangeController interfaceState]); - [rangeController updateCurrentRangeWithMode:isDisplay ? ASLayoutRangeModeMinimum : __rangeModeForMemoryWarnings]; - [rangeController setNeedsUpdate]; + [rangeController updateCurrentRangeWithMode:isDisplay ? ASLayoutRangeModeVisibleOnly : __rangeModeForMemoryWarnings]; + // There's no need to call needs update as updateCurrentRangeWithMode sets this if necessary. [rangeController updateIfNeeded]; } @@ -568,7 +581,7 @@ + (void)didEnterBackground:(NSNotification *)notification __ApplicationState = UIApplicationStateBackground; for (ASRangeController *rangeController in allRangeControllers) { // Trigger a range update immediately, as we may not be allowed by the system to run the update block scheduled by changing range mode. - [rangeController setNeedsUpdate]; + // There's no need to call needs update as updateCurrentRangeWithMode sets this if necessary. [rangeController updateIfNeeded]; } @@ -584,7 +597,7 @@ + (void)willEnterForeground:(NSNotification *)notification for (ASRangeController *rangeController in allRangeControllers) { BOOL isVisible = ASInterfaceStateIncludesVisible([rangeController interfaceState]); [rangeController updateCurrentRangeWithMode:isVisible ? ASLayoutRangeModeMinimum : ASLayoutRangeModeVisibleOnly]; - [rangeController setNeedsUpdate]; + // There's no need to call needs update as updateCurrentRangeWithMode sets this if necessary. [rangeController updateIfNeeded]; } @@ -609,7 +622,7 @@ - (NSString *)descriptionWithIndexPaths:(NSArray *)indexPaths { NSMutableString *description = [NSMutableString stringWithFormat:@"%@ %@", [super description], @" allPreviousIndexPaths:\n"]; for (NSIndexPath *indexPath in indexPaths) { - ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; + ASDisplayNode *node = [[_dataSource elementMapForRangeController:self] elementForItemAtIndexPath:indexPath].nodeIfAllocated; ASInterfaceState interfaceState = node.interfaceState; BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState); BOOL inDisplay = ASInterfaceStateIncludesDisplay(interfaceState); diff --git a/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h b/Source/Details/ASRangeControllerUpdateRangeProtocol+Beta.h similarity index 95% rename from AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h rename to Source/Details/ASRangeControllerUpdateRangeProtocol+Beta.h index 13f03b3e12..64b0b06f6c 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h +++ b/Source/Details/ASRangeControllerUpdateRangeProtocol+Beta.h @@ -8,6 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #import @protocol ASRangeControllerUpdateRangeProtocol diff --git a/AsyncDisplayKit/Details/ASScrollDirection.h b/Source/Details/ASScrollDirection.h similarity index 94% rename from AsyncDisplayKit/Details/ASScrollDirection.h rename to Source/Details/ASScrollDirection.h index 7375e5d9be..cd02831269 100644 --- a/AsyncDisplayKit/Details/ASScrollDirection.h +++ b/Source/Details/ASScrollDirection.h @@ -9,10 +9,9 @@ // #import +#import -#import "ASBaseDefines.h" - -#include +#import NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/Details/ASScrollDirection.m b/Source/Details/ASScrollDirection.m similarity index 98% rename from AsyncDisplayKit/Details/ASScrollDirection.m rename to Source/Details/ASScrollDirection.m index 9e9a927c85..27f22ea162 100644 --- a/AsyncDisplayKit/Details/ASScrollDirection.m +++ b/Source/Details/ASScrollDirection.m @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASScrollDirection.h" +#import const ASScrollDirection ASScrollDirectionHorizontalDirections = ASScrollDirectionLeft | ASScrollDirectionRight; const ASScrollDirection ASScrollDirectionVerticalDirections = ASScrollDirectionUp | ASScrollDirectionDown; diff --git a/AsyncDisplayKit/Details/ASSectionContext.h b/Source/Details/ASSectionContext.h similarity index 94% rename from AsyncDisplayKit/Details/ASSectionContext.h rename to Source/Details/ASSectionContext.h index ad444b8808..27be63f1c8 100644 --- a/AsyncDisplayKit/Details/ASSectionContext.h +++ b/Source/Details/ASSectionContext.h @@ -10,6 +10,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import + @class ASCollectionView; @protocol ASSectionContext diff --git a/Source/Details/ASTableLayoutController.h b/Source/Details/ASTableLayoutController.h new file mode 100644 index 0000000000..61a50fba1d --- /dev/null +++ b/Source/Details/ASTableLayoutController.h @@ -0,0 +1,30 @@ +// +// ASTableLayoutController.h +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class UITableView; + +/** + * A layout controller designed for use with UITableView. + */ +AS_SUBCLASSING_RESTRICTED +@interface ASTableLayoutController : ASAbstractLayoutController + +@property (nonatomic, weak, readonly) UITableView *tableView; + +- (instancetype)initWithTableView:(UITableView *)tableView; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASTableLayoutController.m b/Source/Details/ASTableLayoutController.m new file mode 100644 index 0000000000..b0d24564e3 --- /dev/null +++ b/Source/Details/ASTableLayoutController.m @@ -0,0 +1,55 @@ +// +// ASTableLayoutController.m +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import + +#import +#import + +@interface ASTableLayoutController() +@end + +@implementation ASTableLayoutController + +- (instancetype)initWithTableView:(UITableView *)tableView +{ + if (!(self = [super init])) { + return nil; + } + _tableView = tableView; + return self; +} + +#pragma mark - ASLayoutController + +- (NSSet *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map +{ + CGRect bounds = _tableView.bounds; + + ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; + CGRect rangeBounds = CGRectExpandToRangeWithScrollableDirections(bounds, tuningParameters, ASScrollDirectionVerticalDirections, scrollDirection); + NSArray *array = [_tableView indexPathsForRowsInRect:rangeBounds]; + return ASSetByFlatMapping(array, NSIndexPath *indexPath, [map elementForItemAtIndexPath:indexPath]); +} + +- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet *__autoreleasing _Nullable *)displaySet preloadSet:(NSSet *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map +{ + if (displaySet == NULL || preloadSet == NULL) { + return; + } + + *displaySet = [self elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay map:map]; + *preloadSet = [self elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypePreload map:map]; + return; +} + +@end diff --git a/AsyncDisplayKit/Details/ASThread.h b/Source/Details/ASThread.h similarity index 76% rename from AsyncDisplayKit/Details/ASThread.h rename to Source/Details/ASThread.h index 73c17163f5..9111242f09 100644 --- a/AsyncDisplayKit/Details/ASThread.h +++ b/Source/Details/ASThread.h @@ -29,6 +29,17 @@ static inline BOOL ASDisplayNodeThreadIsMain() #ifdef __cplusplus #define TIME_LOCKER 0 +/** + * Enable this flag to collect information on the owning thread and ownership level of a mutex. + * These properties are useful to determine if a mutext has been acquired and in case of a recursive mutex, how many times that happened. + * + * This flag also enable locking assertions (e.g ASDisplayNodeAssertLockUnownedByCurrentThread(node)). + * The assertions are useful when you want to indicate and enforce the locking policy/expectation of methods. + * To determine when and which methods acquired a (recursive) mutex (to debug deadlocks, for example), + * put breakpoints at some assertions. When the breakpoints hit, walk through stack trace frames + * and check ownership count of the mutex. + */ +#define CHECK_LOCKING_SAFETY 0 #if TIME_LOCKER #import @@ -53,6 +64,18 @@ static inline BOOL ASDisplayNodeThreadIsMain() _Pragma("clang diagnostic pop"); \ } while (0) +/** + * Assert if the current thread owns a mutex. + * This assertion is useful when you want to indicate and enforce the locking policy/expectation of methods. + * To determine when and which methods acquired a (recursive) mutex (to debug deadlocks, for example), + * put breakpoints at some of these assertions. When the breakpoints hit, walk through stack trace frames + * and check ownership count of the mutex. + */ +#if CHECK_LOCKING_SAFETY +#define ASDisplayNodeAssertLockUnownedByCurrentThread(lock) ASDisplayNodeAssertFalse(lock.ownedByCurrentThread()) +#else +#define ASDisplayNodeAssertLockUnownedByCurrentThread(lock) +#endif namespace ASDN { @@ -174,6 +197,10 @@ namespace ASDN { ~Mutex () { ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_destroy (&_m)); +#if CHECK_LOCKING_SAFETY + _owner = 0; + _count = 0; +#endif } Mutex (const Mutex&) = delete; @@ -181,14 +208,45 @@ namespace ASDN { void lock () { ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_lock (this->mutex())); +#if CHECK_LOCKING_SAFETY + mach_port_t thread_id = pthread_mach_thread_np(pthread_self()); + if (thread_id != _owner) { + // New owner. Since this mutex can't be acquired by another thread if there is an existing owner, _owner and _count must be 0. + assert(0 == _owner); + assert(0 == _count); + _owner = thread_id; + } else { + // Existing owner tries to reacquire this (recursive) mutex. _count must already be positive. + assert(_count > 0); + } + ++_count; +#endif } void unlock () { +#if CHECK_LOCKING_SAFETY + mach_port_t thread_id = pthread_mach_thread_np(pthread_self()); + // Unlocking a mutex on an unowning thread causes undefined behaviour. Assert and fail early. + assert(thread_id == _owner); + // Current thread owns this mutex. _count must be positive. + assert(_count > 0); + --_count; + if (0 == _count) { + // Current thread is no longer the owner. + _owner = 0; + } +#endif ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_unlock (this->mutex())); } pthread_mutex_t *mutex () { return &_m; } +#if CHECK_LOCKING_SAFETY + bool ownedByCurrentThread() { + return _count > 0 && pthread_mach_thread_np(pthread_self()) == _owner; + } +#endif + protected: explicit Mutex (bool recursive) { if (!recursive) { @@ -200,10 +258,18 @@ namespace ASDN { ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_init (&_m, &attr)); ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutexattr_destroy (&attr)); } +#if CHECK_LOCKING_SAFETY + _owner = 0; + _count = 0; +#endif } private: pthread_mutex_t _m; +#if CHECK_LOCKING_SAFETY + mach_port_t _owner; + uint32_t _count; +#endif }; /** diff --git a/AsyncDisplayKit/Details/ASTraceEvent.h b/Source/Details/ASTraceEvent.h similarity index 91% rename from AsyncDisplayKit/Details/ASTraceEvent.h rename to Source/Details/ASTraceEvent.h index 5c77e418c7..db182ccfc4 100644 --- a/AsyncDisplayKit/Details/ASTraceEvent.h +++ b/Source/Details/ASTraceEvent.h @@ -7,9 +7,11 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN +AS_SUBCLASSING_RESTRICTED @interface ASTraceEvent : NSObject /** diff --git a/AsyncDisplayKit/Details/ASTraceEvent.m b/Source/Details/ASTraceEvent.m similarity index 98% rename from AsyncDisplayKit/Details/ASTraceEvent.m rename to Source/Details/ASTraceEvent.m index 85ff69ed0d..056129841f 100644 --- a/AsyncDisplayKit/Details/ASTraceEvent.m +++ b/Source/Details/ASTraceEvent.m @@ -6,7 +6,7 @@ // Copyright © 2016 Facebook. All rights reserved. // -#import "ASTraceEvent.h" +#import #import static NSString *const ASTraceEventThreadDescriptionKey = @"ASThreadTraceEventDescription"; diff --git a/Source/Details/ASTraitCollection.h b/Source/Details/ASTraitCollection.h new file mode 100644 index 0000000000..1e2a83f91c --- /dev/null +++ b/Source/Details/ASTraitCollection.h @@ -0,0 +1,157 @@ +// +// ASTraitCollection.h +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + + +#import +#import + +@class ASTraitCollection; +@protocol ASLayoutElement; + +NS_ASSUME_NONNULL_BEGIN + +ASDISPLAYNODE_EXTERN_C_BEGIN + +#pragma mark - ASPrimitiveTraitCollection + +typedef struct ASPrimitiveTraitCollection { + CGFloat displayScale; + UIUserInterfaceSizeClass horizontalSizeClass; + UIUserInterfaceIdiom userInterfaceIdiom; + UIUserInterfaceSizeClass verticalSizeClass; + UIForceTouchCapability forceTouchCapability; + + CGSize containerSize; +} ASPrimitiveTraitCollection; + +/** + * Creates ASPrimitiveTraitCollection with default values. + */ +extern ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault(); + +/** + * Creates a ASPrimitiveTraitCollection from a given UITraitCollection. + */ +extern ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection); + + +/** + * Compares two ASPrimitiveTraitCollection to determine if they are the same. + */ +extern BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs); + +/** + * Returns a string representation of a ASPrimitiveTraitCollection. + */ +extern NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits); + +/** + * This function will walk the layout element hierarchy and updates the layout element trait collection for every + * layout element within the hierarchy. + */ +extern void ASTraitCollectionPropagateDown(id root, ASPrimitiveTraitCollection traitCollection); + +/// For backward compatibility reasons we redefine the old layout element trait collection struct name +#define ASEnvironmentTraitCollection ASPrimitiveTraitCollection +#define ASEnvironmentTraitCollectionMakeDefault ASPrimitiveTraitCollectionMakeDefault + +ASDISPLAYNODE_EXTERN_C_END + +/** + * Abstraction on top of UITraitCollection for propagation within AsyncDisplayKit-Layout + */ +@protocol ASTraitEnvironment + +/** + * Returns a struct-representation of the environment's ASEnvironmentDisplayTraits. This only exists as a internal + * convenience method. Users should access the trait collections through the NSObject based asyncTraitCollection API + */ +- (ASPrimitiveTraitCollection)primitiveTraitCollection; + +/** + * Sets a trait collection on this environment state. + */ +- (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection; + +/** + * Returns an NSObject-representation of the environment's ASEnvironmentDisplayTraits + */ +- (ASTraitCollection *)asyncTraitCollection; + +/** + * Deprecated and should be replaced by the methods from above + */ +- (ASEnvironmentTraitCollection)environmentTraitCollection; +- (void)setEnvironmentTraitCollection:(ASEnvironmentTraitCollection)traitCollection; + + +@end + +#define ASPrimitiveTraitCollectionDeprecatedImplementation \ +- (ASEnvironmentTraitCollection)environmentTraitCollection\ +{\ + return self.primitiveTraitCollection;\ +}\ +- (void)setEnvironmentTraitCollection:(ASEnvironmentTraitCollection)traitCollection\ +{\ + [self setPrimitiveTraitCollection:traitCollection];\ +}\ + +#define ASLayoutElementCollectionTableSetTraitCollection(lock) \ +- (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection\ +{\ + ASDN::MutexLocker l(lock);\ +\ + ASPrimitiveTraitCollection oldTraits = self.primitiveTraitCollection;\ + [super setPrimitiveTraitCollection:traitCollection];\ +\ + /* Extra Trait Collection Handling */\ +\ + /* If the node is not loaded yet don't do anything as otherwise the access of the view will trigger a load */\ + if (! self.isNodeLoaded) { return; }\ +\ + ASPrimitiveTraitCollection currentTraits = self.primitiveTraitCollection;\ + if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(currentTraits, oldTraits) == NO) {\ + [self.dataController environmentDidChange];\ + }\ +}\ + +#pragma mark - ASTraitCollection + +AS_SUBCLASSING_RESTRICTED +@interface ASTraitCollection : NSObject + +@property (nonatomic, assign, readonly) CGFloat displayScale; +@property (nonatomic, assign, readonly) UIUserInterfaceSizeClass horizontalSizeClass; +@property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom; +@property (nonatomic, assign, readonly) UIUserInterfaceSizeClass verticalSizeClass; +@property (nonatomic, assign, readonly) UIForceTouchCapability forceTouchCapability; +@property (nonatomic, assign, readonly) CGSize containerSize; + ++ (ASTraitCollection *)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits; + ++ (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection + containerSize:(CGSize)windowSize; + + ++ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + containerSize:(CGSize)windowSize; + + +- (ASPrimitiveTraitCollection)primitiveTraitCollection; +- (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m new file mode 100644 index 0000000000..2435cb75cd --- /dev/null +++ b/Source/Details/ASTraitCollection.m @@ -0,0 +1,196 @@ +// +// ASTraitCollection.m +// AsyncDisplayKit +// +// Created by Ricky Cancro on 5/4/16. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import +#import +#import + +#pragma mark - ASPrimitiveTraitCollection + +extern void ASTraitCollectionPropagateDown(id root, ASPrimitiveTraitCollection traitCollection) { + ASLayoutElementPerformBlockOnEveryElement(root, ^(id _Nonnull element) { + element.primitiveTraitCollection = traitCollection; + }); +} + +ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault() +{ + return (ASPrimitiveTraitCollection) { + // Default values can be defined in here + .userInterfaceIdiom = UIUserInterfaceIdiomUnspecified, + .containerSize = CGSizeZero, + }; +} + +ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) +{ + ASPrimitiveTraitCollection environmentTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); + environmentTraitCollection.displayScale = traitCollection.displayScale; + environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; + environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; + environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; + if (AS_AT_LEAST_IOS9) { + environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; + } + return environmentTraitCollection; +} + +BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) +{ + return + lhs.verticalSizeClass == rhs.verticalSizeClass && + lhs.horizontalSizeClass == rhs.horizontalSizeClass && + lhs.displayScale == rhs.displayScale && + lhs.userInterfaceIdiom == rhs.userInterfaceIdiom && + lhs.forceTouchCapability == rhs.forceTouchCapability && + CGSizeEqualToSize(lhs.containerSize, rhs.containerSize); +} + +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceIdiom(UIUserInterfaceIdiom idiom) { + switch (idiom) { + case UIUserInterfaceIdiomTV: + return @"TV"; + case UIUserInterfaceIdiomPad: + return @"Pad"; + case UIUserInterfaceIdiomPhone: + return @"Phone"; + case UIUserInterfaceIdiomCarPlay: + return @"CarPlay"; + default: + return @"Unspecified"; + } +} + +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIForceTouchCapability(UIForceTouchCapability capability) { + switch (capability) { + case UIForceTouchCapabilityAvailable: + return @"Available"; + case UIForceTouchCapabilityUnavailable: + return @"Unavailable"; + default: + return @"Unknown"; + } +} + +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceSizeClass(UIUserInterfaceSizeClass sizeClass) { + switch (sizeClass) { + case UIUserInterfaceSizeClassCompact: + return @"Compact"; + case UIUserInterfaceSizeClassRegular: + return @"Regular"; + default: + return @"Unspecified"; + } +} + +NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits) +{ + NSMutableArray *props = [NSMutableArray array]; + [props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }]; + [props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }]; + [props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }]; + [props addObject:@{ @"verticalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.verticalSizeClass) }]; + [props addObject:@{ @"forceTouchCapability": AS_NSStringFromUIForceTouchCapability(traits.forceTouchCapability) }]; + return ASObjectDescriptionMakeWithoutObject(props); +} + +#pragma mark - ASTraitCollection + +@implementation ASTraitCollection + +- (instancetype)initWithDisplayScale:(CGFloat)displayScale + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + containerSize:(CGSize)windowSize +{ + self = [super init]; + if (self) { + _displayScale = displayScale; + _userInterfaceIdiom = userInterfaceIdiom; + _horizontalSizeClass = horizontalSizeClass; + _verticalSizeClass = verticalSizeClass; + _forceTouchCapability = forceTouchCapability; + _containerSize = windowSize; + } + return self; +} + ++ (instancetype)traitCollectionWithDisplayScale:(CGFloat)displayScale + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + containerSize:(CGSize)windowSize +{ + return [[self alloc] initWithDisplayScale:displayScale + userInterfaceIdiom:userInterfaceIdiom + horizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + forceTouchCapability:forceTouchCapability + containerSize:windowSize]; +} + ++ (instancetype)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits +{ + return [self traitCollectionWithDisplayScale:traits.displayScale + userInterfaceIdiom:traits.userInterfaceIdiom + horizontalSizeClass:traits.horizontalSizeClass + verticalSizeClass:traits.verticalSizeClass + forceTouchCapability:traits.forceTouchCapability + containerSize:traits.containerSize]; +} + ++ (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection + containerSize:(CGSize)windowSize +{ + UIForceTouchCapability forceTouch = AS_AT_LEAST_IOS9 ? traitCollection.forceTouchCapability : UIForceTouchCapabilityUnknown; + return [self traitCollectionWithDisplayScale:traitCollection.displayScale + userInterfaceIdiom:traitCollection.userInterfaceIdiom + horizontalSizeClass:traitCollection.horizontalSizeClass + verticalSizeClass:traitCollection.verticalSizeClass + forceTouchCapability:forceTouch + containerSize:windowSize]; +} + +- (ASPrimitiveTraitCollection)primitiveTraitCollection +{ + return (ASPrimitiveTraitCollection) { + .displayScale = self.displayScale, + .horizontalSizeClass = self.horizontalSizeClass, + .userInterfaceIdiom = self.userInterfaceIdiom, + .verticalSizeClass = self.verticalSizeClass, + .forceTouchCapability = self.forceTouchCapability, + .containerSize = self.containerSize, + }; +} + +- (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection +{ + if (self == traitCollection) { + return YES; + } + + return self.displayScale == traitCollection.displayScale && + self.horizontalSizeClass == traitCollection.horizontalSizeClass && + self.verticalSizeClass == traitCollection.verticalSizeClass && + self.userInterfaceIdiom == traitCollection.userInterfaceIdiom && + CGSizeEqualToSize(self.containerSize, traitCollection.containerSize) && + self.forceTouchCapability == traitCollection.forceTouchCapability; +} + +@end diff --git a/AsyncDisplayKit/Details/ASWeakProxy.h b/Source/Details/ASWeakProxy.h similarity index 93% rename from AsyncDisplayKit/Details/ASWeakProxy.h rename to Source/Details/ASWeakProxy.h index 95f195e134..7a7d2694d4 100644 --- a/AsyncDisplayKit/Details/ASWeakProxy.h +++ b/Source/Details/ASWeakProxy.h @@ -11,7 +11,9 @@ // #import +#import +AS_SUBCLASSING_RESTRICTED @interface ASWeakProxy : NSProxy /** diff --git a/AsyncDisplayKit/Details/ASWeakProxy.m b/Source/Details/ASWeakProxy.m similarity index 90% rename from AsyncDisplayKit/Details/ASWeakProxy.m rename to Source/Details/ASWeakProxy.m index e17b75146a..de06b9fc62 100644 --- a/AsyncDisplayKit/Details/ASWeakProxy.m +++ b/Source/Details/ASWeakProxy.m @@ -10,9 +10,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASWeakProxy.h" -#import "ASObjectDescriptionHelpers.h" -#import "ASAssert.h" +#import +#import +#import @implementation ASWeakProxy @@ -39,6 +39,11 @@ - (BOOL)respondsToSelector:(SEL)aSelector return [_target respondsToSelector:aSelector]; } +- (BOOL)conformsToProtocol:(Protocol *)aProtocol +{ + return [_target conformsToProtocol:aProtocol]; +} + /// Strangely, this method doesn't get forwarded by ObjC. - (BOOL)isKindOfClass:(Class)aClass { diff --git a/AsyncDisplayKit/Details/ASWeakSet.h b/Source/Details/ASWeakSet.h similarity index 81% rename from AsyncDisplayKit/Details/ASWeakSet.h rename to Source/Details/ASWeakSet.h index de84757351..781cbc0929 100644 --- a/AsyncDisplayKit/Details/ASWeakSet.h +++ b/Source/Details/ASWeakSet.h @@ -15,6 +15,12 @@ NS_ASSUME_NONNULL_BEGIN +/** + * A class similar to NSSet that stores objects weakly. + * Note that this class uses NSPointerFunctionsObjectPointerPersonality – + * that is, it uses shifted pointer for hashing, and identity comparison for equality. + */ +AS_SUBCLASSING_RESTRICTED @interface ASWeakSet<__covariant ObjectType> : NSObject /// Returns YES if the receiver is empty, NO otherwise. @@ -33,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)removeAllObjects; /// Returns a standard *retained* NSArray of all objects. Not free to generate, but useful for iterating over contents. -- (NSArray *)allObjects AS_WARN_UNUSED_RESULT; +- (NSArray *)allObjects AS_WARN_UNUSED_RESULT; /** * How many objects are contained in this set. diff --git a/AsyncDisplayKit/Details/ASWeakSet.m b/Source/Details/ASWeakSet.m similarity index 88% rename from AsyncDisplayKit/Details/ASWeakSet.m rename to Source/Details/ASWeakSet.m index c68b730db9..ee21f2b956 100644 --- a/AsyncDisplayKit/Details/ASWeakSet.m +++ b/Source/Details/ASWeakSet.m @@ -10,7 +10,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASWeakSet.h" +#import @interface ASWeakSet<__covariant ObjectType> () @property (nonatomic, strong, readonly) NSHashTable *hashTable; @@ -22,7 +22,7 @@ - (instancetype)init { self = [super init]; if (self) { - _hashTable = [NSHashTable weakObjectsHashTable]; + _hashTable = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality]; } return self; } @@ -59,7 +59,7 @@ - (BOOL)isEmpty /** Note: The `count` property of NSHashTable is unreliable - in the case of weak-object hash tables because entries + in the case of weak-memory hash tables because entries that have been deallocated are not removed immediately. In order to get the true count we have to fall back to using diff --git a/AsyncDisplayKit/Details/CoreGraphics+ASConvenience.h b/Source/Details/CoreGraphics+ASConvenience.h similarity index 52% rename from AsyncDisplayKit/Details/CoreGraphics+ASConvenience.h rename to Source/Details/CoreGraphics+ASConvenience.h index e657158c99..65535112bf 100644 --- a/AsyncDisplayKit/Details/CoreGraphics+ASConvenience.h +++ b/Source/Details/CoreGraphics+ASConvenience.h @@ -9,11 +9,12 @@ // #import + #import +#import + +#import -#import "ASBaseDefines.h" -#import "ASLayoutController.h" -#include "tgmath.h" #ifndef CGFLOAT_EPSILON #if CGFLOAT_IS_DOUBLE @@ -50,23 +51,6 @@ ASDISPLAYNODE_INLINE BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CG return fabs(size1.width - size2.width) < delta && fabs(size1.height - size2.height) < delta; }; -struct ASDirectionalScreenfulBuffer { - CGFloat positiveDirection; // Positive relative to iOS Core Animation layer coordinate space. - CGFloat negativeDirection; -}; -typedef struct ASDirectionalScreenfulBuffer ASDirectionalScreenfulBuffer; - -ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection, - ASRangeTuningParameters rangeTuningParameters); - -ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection, - ASRangeTuningParameters rangeTuningParameters); - -CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect, - ASRangeTuningParameters tuningParameters, - ASScrollDirection scrollableDirections, - ASScrollDirection scrollDirection); - ASDISPLAYNODE_EXTERN_C_END NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h b/Source/Details/CoreGraphics+ASConvenience.m similarity index 62% rename from AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h rename to Source/Details/CoreGraphics+ASConvenience.m index 461c0966fd..1cca126371 100644 --- a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h +++ b/Source/Details/CoreGraphics+ASConvenience.m @@ -1,5 +1,5 @@ // -// UICollectionViewLayout+ASConvenience.h +// CGRect+ASConvenience.m // AsyncDisplayKit // // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. @@ -8,14 +8,5 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import +#import -NS_ASSUME_NONNULL_BEGIN - -@interface UICollectionViewLayout (ASConvenience) - -- (BOOL)asdk_isFlowLayout; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/NSArray+Diffing.h b/Source/Details/NSArray+Diffing.h similarity index 100% rename from AsyncDisplayKit/Details/NSArray+Diffing.h rename to Source/Details/NSArray+Diffing.h diff --git a/AsyncDisplayKit/Details/NSArray+Diffing.m b/Source/Details/NSArray+Diffing.m similarity index 85% rename from AsyncDisplayKit/Details/NSArray+Diffing.m rename to Source/Details/NSArray+Diffing.m index d7352a5c70..b1b3b64e98 100644 --- a/AsyncDisplayKit/Details/NSArray+Diffing.m +++ b/Source/Details/NSArray+Diffing.m @@ -10,8 +10,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "NSArray+Diffing.h" -#import "ASAssert.h" +#import +#import @implementation NSArray (Diffing) @@ -60,18 +60,19 @@ - (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL NSInteger arrayCount = array.count; // Allocate the diff map in the heap so we don't blow the stack for large arrays. - NSInteger (*lengths)[arrayCount+1] = NULL; - size_t lengthsSize = ((selfCount+1) * sizeof(*lengths)); - // Would rather use initWithCapacity: to skip the zeroing, but TECHNICALLY - // `mutableBytes` is only guaranteed to be non-NULL if the data object has a non-zero length. - NS_VALID_UNTIL_END_OF_SCOPE NSMutableData *lengthsData = [[NSMutableData alloc] initWithLength:lengthsSize]; - lengths = lengthsData.mutableBytes; + NSInteger **lengths = NULL; + lengths = (NSInteger **)malloc(sizeof(NSInteger*) * (selfCount+1)); if (lengths == NULL) { - ASDisplayNodeFailAssert(@"Failed to allocate memory for diffing with size %tu", lengthsSize); + ASDisplayNodeFailAssert(@"Failed to allocate memory for diffing"); return nil; } for (NSInteger i = 0; i <= selfCount; i++) { + lengths[i] = (NSInteger *)malloc(sizeof(NSInteger) * (arrayCount+1)); + if (lengths[i] == NULL) { + ASDisplayNodeFailAssert(@"Failed to allocate memory for diffing"); + return nil; + } id selfObj = i > 0 ? self[i-1] : nil; for (NSInteger j = 0; j <= arrayCount; j++) { if (i == 0 || j == 0) { @@ -96,7 +97,11 @@ - (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL j--; } } - + + for (NSInteger i = 0; i <= selfCount; i++) { + free(lengths[i]); + } + free(lengths); return common; } diff --git a/AsyncDisplayKit/Details/NSIndexSet+ASHelpers.h b/Source/Details/NSIndexSet+ASHelpers.h similarity index 76% rename from AsyncDisplayKit/Details/NSIndexSet+ASHelpers.h rename to Source/Details/NSIndexSet+ASHelpers.h index 179e685639..a816b102f4 100644 --- a/AsyncDisplayKit/Details/NSIndexSet+ASHelpers.h +++ b/Source/Details/NSIndexSet+ASHelpers.h @@ -22,4 +22,9 @@ - (NSString *)as_smallDescription; +/// Returns all the section indexes contained in the index paths array. ++ (NSIndexSet *)as_sectionsFromIndexPaths:(NSArray *)indexPaths; + +- (NSArray *)as_filterIndexPathsBySection:(id)indexPaths; + @end diff --git a/AsyncDisplayKit/Details/NSIndexSet+ASHelpers.m b/Source/Details/NSIndexSet+ASHelpers.m similarity index 77% rename from AsyncDisplayKit/Details/NSIndexSet+ASHelpers.m rename to Source/Details/NSIndexSet+ASHelpers.m index de3314c07a..756d038a8c 100644 --- a/AsyncDisplayKit/Details/NSIndexSet+ASHelpers.m +++ b/Source/Details/NSIndexSet+ASHelpers.m @@ -6,9 +6,11 @@ // Copyright © 2016 Facebook. All rights reserved. // + +// UIKit indexPath helpers #import -#import "NSIndexSet+ASHelpers.h" +#import @implementation NSIndexSet (ASHelpers) @@ -77,4 +79,24 @@ - (NSString *)as_smallDescription return result; } ++ (NSIndexSet *)as_sectionsFromIndexPaths:(NSArray *)indexPaths +{ + NSMutableIndexSet *result = [NSMutableIndexSet indexSet]; + for (NSIndexPath *indexPath in indexPaths) { + [result addIndex:indexPath.section]; + } + return result; +} + +- (NSArray *)as_filterIndexPathsBySection:(id)indexPaths +{ + NSMutableArray *result = [NSMutableArray array]; + for (NSIndexPath *indexPath in indexPaths) { + if ([self containsIndex:indexPath.section]) { + [result addObject:indexPath]; + } + } + return result; +} + @end diff --git a/AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.h b/Source/Details/NSMutableAttributedString+TextKitAdditions.h similarity index 100% rename from AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.h rename to Source/Details/NSMutableAttributedString+TextKitAdditions.h diff --git a/AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.m b/Source/Details/NSMutableAttributedString+TextKitAdditions.m similarity index 95% rename from AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.m rename to Source/Details/NSMutableAttributedString+TextKitAdditions.m index e28c014cc2..ab032903d1 100644 --- a/AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.m +++ b/Source/Details/NSMutableAttributedString+TextKitAdditions.m @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "NSMutableAttributedString+TextKitAdditions.h" +#import @implementation NSMutableAttributedString (TextKitAdditions) diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h b/Source/Details/Transactions/_ASAsyncTransaction.h similarity index 100% rename from AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h rename to Source/Details/Transactions/_ASAsyncTransaction.h diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.mm b/Source/Details/Transactions/_ASAsyncTransaction.mm similarity index 98% rename from AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.mm rename to Source/Details/Transactions/_ASAsyncTransaction.mm index 55824942ce..f35e83db80 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.mm +++ b/Source/Details/Transactions/_ASAsyncTransaction.mm @@ -11,11 +11,11 @@ // We need this import for UITrackingRunLoopMode #import -#import "_ASAsyncTransaction.h" -#import "_ASAsyncTransactionGroup.h" -#import "ASAssert.h" -#import "ASLog.h" -#import "ASThread.h" +#import +#import +#import +#import +#import #import #import #import diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer+Private.h b/Source/Details/Transactions/_ASAsyncTransactionContainer+Private.h similarity index 89% rename from AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer+Private.h rename to Source/Details/Transactions/_ASAsyncTransactionContainer+Private.h index 579362baca..ca81f88d66 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer+Private.h +++ b/Source/Details/Transactions/_ASAsyncTransactionContainer+Private.h @@ -8,10 +8,13 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import +#import +#import NS_ASSUME_NONNULL_BEGIN +@class _ASAsyncTransaction; + @interface CALayer (ASAsyncTransactionContainerTransactions) @property (nonatomic, strong, nullable, setter=asyncdisplaykit_setAsyncLayerTransactions:) NSHashTable<_ASAsyncTransaction *> *asyncdisplaykit_asyncLayerTransactions; diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.h b/Source/Details/Transactions/_ASAsyncTransactionContainer.h similarity index 99% rename from AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.h rename to Source/Details/Transactions/_ASAsyncTransactionContainer.h index cb83c2920e..92037441ee 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.h +++ b/Source/Details/Transactions/_ASAsyncTransactionContainer.h @@ -8,6 +8,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#pragma once + #import NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.m b/Source/Details/Transactions/_ASAsyncTransactionContainer.m similarity index 96% rename from AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.m rename to Source/Details/Transactions/_ASAsyncTransactionContainer.m index 8dbd043489..c931b1f344 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.m +++ b/Source/Details/Transactions/_ASAsyncTransactionContainer.m @@ -8,11 +8,11 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "_ASAsyncTransactionContainer+Private.h" +#import +#import -#import "_ASAsyncTransaction.h" -#import "_ASAsyncTransactionContainer.h" -#import "_ASAsyncTransactionGroup.h" +#import +#import #import static const char *ASDisplayNodeAssociatedTransactionsKey = "ASAssociatedTransactions"; diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h b/Source/Details/Transactions/_ASAsyncTransactionGroup.h similarity index 96% rename from AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h rename to Source/Details/Transactions/_ASAsyncTransactionGroup.h index b77b8241b3..44b155c36b 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h +++ b/Source/Details/Transactions/_ASAsyncTransactionGroup.h @@ -8,6 +8,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import + NS_ASSUME_NONNULL_BEGIN @class _ASAsyncTransaction; diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m b/Source/Details/Transactions/_ASAsyncTransactionGroup.m similarity index 93% rename from AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m rename to Source/Details/Transactions/_ASAsyncTransactionGroup.m index 9f226c3619..c7c920f811 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m +++ b/Source/Details/Transactions/_ASAsyncTransactionGroup.m @@ -8,11 +8,12 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASAssert.h" +#import -#import "_ASAsyncTransaction.h" -#import "_ASAsyncTransactionGroup.h" -#import "_ASAsyncTransactionContainer+Private.h" +#import +#import +#import +#import static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info); diff --git a/Source/Details/UICollectionViewLayout+ASConvenience.h b/Source/Details/UICollectionViewLayout+ASConvenience.h new file mode 100644 index 0000000000..2b1c4b28f3 --- /dev/null +++ b/Source/Details/UICollectionViewLayout+ASConvenience.h @@ -0,0 +1,29 @@ +// +// UICollectionViewLayout+ASConvenience.h +// AsyncDisplayKit +// +// 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 + +@protocol ASCollectionViewLayoutInspecting; + +NS_ASSUME_NONNULL_BEGIN + +@interface UICollectionViewLayout (ASLayoutInspectorProviding) + +/** + * You can override this method on your @c UICollectionViewLayout subclass to + * return a layout inspector tailored to your layout. + * + * It's fine to return @c self. You must not return @c nil. + */ +- (id)asdk_layoutInspector; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/UICollectionViewLayout+ASConvenience.m b/Source/Details/UICollectionViewLayout+ASConvenience.m new file mode 100644 index 0000000000..ef0ce990fc --- /dev/null +++ b/Source/Details/UICollectionViewLayout+ASConvenience.m @@ -0,0 +1,29 @@ +// +// UICollectionViewLayout+ASConvenience.m +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import + +#import + +@implementation UICollectionViewLayout (ASLayoutInspectorProviding) + +- (id)asdk_layoutInspector +{ + UICollectionViewFlowLayout *flow = ASDynamicCast(self, UICollectionViewFlowLayout); + if (flow != nil) { + return [[ASCollectionViewFlowLayoutInspector alloc] initWithFlowLayout:flow]; + } else { + return [[ASCollectionViewLayoutInspector alloc] init]; + } +} + +@end diff --git a/AsyncDisplayKit/Details/UIView+ASConvenience.h b/Source/Details/UIView+ASConvenience.h similarity index 99% rename from AsyncDisplayKit/Details/UIView+ASConvenience.h rename to Source/Details/UIView+ASConvenience.h index 24a9021cfd..9c918689ec 100644 --- a/AsyncDisplayKit/Details/UIView+ASConvenience.h +++ b/Source/Details/UIView+ASConvenience.h @@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)setNeedsDisplay; - (void)setNeedsLayout; +- (void)layoutIfNeeded; @end diff --git a/Source/Details/_ASCollectionReusableView.h b/Source/Details/_ASCollectionReusableView.h new file mode 100644 index 0000000000..9f5bbd6f86 --- /dev/null +++ b/Source/Details/_ASCollectionReusableView.h @@ -0,0 +1,22 @@ +// +// _ASCollectionReusableView.h +// AsyncDisplayKit +// +// Created by Phil Larson on 4/10/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +@class ASCellNode; + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED +@interface _ASCollectionReusableView : UICollectionReusableView +@property (nonatomic, weak) ASCellNode *node; +@property (nonatomic, strong, nullable) UICollectionViewLayoutAttributes *layoutAttributes; +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/_ASCollectionReusableView.m b/Source/Details/_ASCollectionReusableView.m new file mode 100644 index 0000000000..50651eb797 --- /dev/null +++ b/Source/Details/_ASCollectionReusableView.m @@ -0,0 +1,82 @@ +// +// _ASCollectionReusableView.m +// AsyncDisplayKit +// +// Created by Phil Larson on 4/10/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "_ASCollectionReusableView.h" +#import "ASCellNode+Internal.h" +#import + +@implementation _ASCollectionReusableView + +- (void)setNode:(ASCellNode *)node +{ + ASDisplayNodeAssertMainThread(); + node.layoutAttributes = _layoutAttributes; + _node = node; +} + +- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes +{ + _layoutAttributes = layoutAttributes; + _node.layoutAttributes = layoutAttributes; +} + +- (void)prepareForReuse +{ + self.layoutAttributes = nil; + + // Need to clear node pointer before UIKit calls setSelected:NO / setHighlighted:NO on its cells + self.node = nil; + [super prepareForReuse]; +} + +/** + * In the initial case, this is called by UICollectionView during cell dequeueing, before + * we get a chance to assign a node to it, so we must be sure to set these layout attributes + * on our node when one is next assigned to us in @c setNode: . Since there may be cases when we _do_ already + * have our node assigned e.g. during a layout update for existing cells, we also attempt + * to update it now. + */ +- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes +{ + self.layoutAttributes = layoutAttributes; +} + +/** + * Keep our node filling our content view. + */ +- (void)layoutSubviews +{ + [super layoutSubviews]; + self.node.frame = self.bounds; +} + +@end + +/** + * A category that makes _ASCollectionReusableView conform to IGListBindable. + * + * We don't need to do anything to bind the view model – the cell node + * serves the same purpose. + */ +#if __has_include() + +#import + +@interface _ASCollectionReusableView (IGListBindable) +@end + +@implementation _ASCollectionReusableView (IGListBindable) + +- (void)bindViewModel:(id)viewModel +{ + // nop +} + +@end + +#endif diff --git a/Source/Details/_ASCollectionViewCell.h b/Source/Details/_ASCollectionViewCell.h new file mode 100644 index 0000000000..57057cb0df --- /dev/null +++ b/Source/Details/_ASCollectionViewCell.h @@ -0,0 +1,22 @@ +// +// _ASCollectionViewCell.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/30/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +@class ASCellNode; + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED +@interface _ASCollectionViewCell : UICollectionViewCell +@property (nonatomic, weak) ASCellNode *node; +@property (nonatomic, strong, nullable) UICollectionViewLayoutAttributes *layoutAttributes; +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/_ASCollectionViewCell.m b/Source/Details/_ASCollectionViewCell.m new file mode 100644 index 0000000000..76709d0c31 --- /dev/null +++ b/Source/Details/_ASCollectionViewCell.m @@ -0,0 +1,98 @@ +// +// _ASCollectionViewCell.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/30/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "_ASCollectionViewCell.h" +#import "ASCellNode+Internal.h" +#import + +@implementation _ASCollectionViewCell + +- (void)setNode:(ASCellNode *)node +{ + ASDisplayNodeAssertMainThread(); + node.layoutAttributes = _layoutAttributes; + _node = node; + + [node __setSelectedFromUIKit:self.selected]; + [node __setHighlightedFromUIKit:self.highlighted]; +} + +- (void)setSelected:(BOOL)selected +{ + [super setSelected:selected]; + [_node __setSelectedFromUIKit:selected]; +} + +- (void)setHighlighted:(BOOL)highlighted +{ + [super setHighlighted:highlighted]; + [_node __setHighlightedFromUIKit:highlighted]; +} + +- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes +{ + _layoutAttributes = layoutAttributes; + _node.layoutAttributes = layoutAttributes; +} + +- (void)prepareForReuse +{ + self.layoutAttributes = nil; + + // Need to clear node pointer before UIKit calls setSelected:NO / setHighlighted:NO on its cells + self.node = nil; + [super prepareForReuse]; +} + +/** + * In the initial case, this is called by UICollectionView during cell dequeueing, before + * we get a chance to assign a node to it, so we must be sure to set these layout attributes + * on our node when one is next assigned to us in @c setNode: . Since there may be cases when we _do_ already + * have our node assigned e.g. during a layout update for existing cells, we also attempt + * to update it now. + */ +- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes +{ + [super applyLayoutAttributes:layoutAttributes]; + self.layoutAttributes = layoutAttributes; +} + +/** + * Keep our node filling our content view. + */ +- (void)layoutSubviews +{ + [super layoutSubviews]; + self.node.frame = self.contentView.bounds; +} + +@end + +/** + * A category that makes _ASCollectionViewCell conform to IGListBindable. + * + * We don't need to do anything to bind the view model – the cell node + * serves the same purpose. + */ +#if __has_include() + +#import + +@interface _ASCollectionViewCell (IGListBindable) +@end + +@implementation _ASCollectionViewCell (IGListBindable) + +- (void)bindViewModel:(id)viewModel +{ + // nop +} + +@end + +#endif diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.h b/Source/Details/_ASDisplayLayer.h similarity index 91% rename from AsyncDisplayKit/Details/_ASDisplayLayer.h rename to Source/Details/_ASDisplayLayer.h index 6cd40af080..cbc0fb38d4 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.h +++ b/Source/Details/_ASDisplayLayer.h @@ -9,13 +9,12 @@ // #import +#import +#import @class ASDisplayNode; @protocol _ASDisplayLayerDelegate; -// Type for the cancellation checker block passed into the async display blocks. YES means the operation has been cancelled, NO means continue. -typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); - @interface _ASDisplayLayer : CALayer /** @@ -103,7 +102,7 @@ typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. @param isRasterizing YES if the layer is being rasterized into another layer, in which case drawRect: probably wants to avoid doing things like filling its bounds with a zero-alpha color to clear the backing store. */ -+ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; ++ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; /** @summary Delegate override to provide new layer contents as a UIImage. @@ -111,19 +110,19 @@ typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. @return A UIImage with contents that are ready to display on the main thread. Make sure that the image is already decoded before returning it here. */ -+ (UIImage *)displayWithParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock; ++ (UIImage *)displayWithParameters:(id)parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock; /** * @abstract instance version of drawRect class method * @see drawRect:withParameters:isCancelled:isRasterizing class method */ -- (void)drawRect:(CGRect)bounds withParameters:(id )parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; +- (void)drawRect:(CGRect)bounds withParameters:(id )parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; /** * @abstract instance version of display class method * @see displayWithParameters:isCancelled class method */ -- (UIImage *)displayWithParameters:(id )parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled; +- (UIImage *)displayWithParameters:(id )parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelled; // Called on the main thread only diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/Source/Details/_ASDisplayLayer.mm similarity index 95% rename from AsyncDisplayKit/Details/_ASDisplayLayer.mm rename to Source/Details/_ASDisplayLayer.mm index a1d74c15fd..8425ff661d 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/Source/Details/_ASDisplayLayer.mm @@ -8,16 +8,16 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "_ASDisplayLayer.h" +#import #import -#import "_ASAsyncTransactionContainer.h" -#import "ASAssert.h" -#import "ASDisplayNode.h" -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASObjectDescriptionHelpers.h" +#import +#import +#import +#import +#import +#import @implementation _ASDisplayLayer { diff --git a/AsyncDisplayKit/Details/_ASDisplayView.h b/Source/Details/_ASDisplayView.h similarity index 99% rename from AsyncDisplayKit/Details/_ASDisplayView.h rename to Source/Details/_ASDisplayView.h index 4e52eb2892..5f1b5879fd 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.h +++ b/Source/Details/_ASDisplayView.h @@ -10,7 +10,6 @@ #import - // This class is only for use by ASDisplayNode and should never be subclassed or used directly. // Note that the "node" property is added to UIView directly via a category in ASDisplayNode. diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/Source/Details/_ASDisplayView.mm similarity index 94% rename from AsyncDisplayKit/Details/_ASDisplayView.mm rename to Source/Details/_ASDisplayView.mm index 5742566538..0fe3f9ff93 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/Source/Details/_ASDisplayView.mm @@ -8,15 +8,16 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "_ASDisplayView.h" -#import "_ASDisplayViewAccessiblity.h" +#import +#import -#import "_ASCoreAnimationExtras.h" -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASObjectDescriptionHelpers.h" -#import "ASLayout.h" +#import +#import +#import +#import +#import +#import +#import @interface _ASDisplayView () @property (nullable, atomic, weak, readwrite) ASDisplayNode *asyncdisplaykit_node; @@ -341,20 +342,20 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer - (void)tintColorDidChange { - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - [super tintColorDidChange]; - - [node tintColorDidChange]; + ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. + [super tintColorDidChange]; + + [node tintColorDidChange]; } - (BOOL)canBecomeFirstResponder { - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node canBecomeFirstResponder]; + ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. + return [node canBecomeFirstResponder]; } - (BOOL)canResignFirstResponder { - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node canResignFirstResponder]; + ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. + return [node canResignFirstResponder]; } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h b/Source/Details/_ASDisplayViewAccessiblity.h similarity index 92% rename from AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h rename to Source/Details/_ASDisplayViewAccessiblity.h index b3dab91ab4..eb9d0f8bfc 100644 --- a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h +++ b/Source/Details/_ASDisplayViewAccessiblity.h @@ -9,6 +9,7 @@ // #import +#import @interface _ASDisplayView (UIAccessibilityContainer) @property (copy, nonatomic) NSArray *accessibleElements; diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm b/Source/Details/_ASDisplayViewAccessiblity.mm similarity index 96% rename from AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm rename to Source/Details/_ASDisplayViewAccessiblity.mm index 641430f613..b6d174a5de 100644 --- a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm +++ b/Source/Details/_ASDisplayViewAccessiblity.mm @@ -10,9 +10,11 @@ #ifndef ASDK_ACCESSIBILITY_DISABLE -#import "_ASDisplayView.h" -#import "ASDisplayNodeExtras.h" -#import "ASDisplayNode+FrameworkPrivate.h" +#import +#import +#import +#import +#import #pragma mark - UIAccessibilityElement diff --git a/Source/IGListAdapter+AsyncDisplayKit.h b/Source/IGListAdapter+AsyncDisplayKit.h new file mode 100644 index 0000000000..9f2cf9ad76 --- /dev/null +++ b/Source/IGListAdapter+AsyncDisplayKit.h @@ -0,0 +1,36 @@ +// +// IGListAdapter+AsyncDisplayKit.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/19/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#if AS_IG_LIST_KIT + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class ASCollectionNode; + +@interface IGListAdapter (AsyncDisplayKit) + +/** + * Connect this list adapter to the given collection node. + * + * @param collectionNode The collection node to drive with this list adapter. + * + * @note This method may only be called once per list adapter, + * and it must be called on the main thread. -[UIViewController init] + * is a good place to call it. This method does not retain the collection node. + */ +- (void)setASDKCollectionNode:(ASCollectionNode *)collectionNode; + +@end + +NS_ASSUME_NONNULL_END + +#endif // AS_IG_LIST_KIT diff --git a/Source/IGListAdapter+AsyncDisplayKit.m b/Source/IGListAdapter+AsyncDisplayKit.m new file mode 100644 index 0000000000..6210e69e33 --- /dev/null +++ b/Source/IGListAdapter+AsyncDisplayKit.m @@ -0,0 +1,52 @@ +// +// IGListAdapter+AsyncDisplayKit.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/19/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#if AS_IG_LIST_KIT + +#import "IGListAdapter+AsyncDisplayKit.h" +#import "ASIGListAdapterBasedDataSource.h" +#import "ASAssert.h" +#import + +@implementation IGListAdapter (AsyncDisplayKit) + +- (void)setASDKCollectionNode:(ASCollectionNode *)collectionNode +{ + ASDisplayNodeAssertMainThread(); + + // Attempt to retrieve previous data source. + ASIGListAdapterBasedDataSource *dataSource = objc_getAssociatedObject(self, _cmd); + // Bomb if we already made one. + if (dataSource != nil) { + ASDisplayNodeFailAssert(@"Attempt to call %@ multiple times on the same list adapter. Not currently allowed!", NSStringFromSelector(_cmd)); + return; + } + + // Make a data source and retain it. + dataSource = [[ASIGListAdapterBasedDataSource alloc] initWithListAdapter:self]; + objc_setAssociatedObject(self, _cmd, dataSource, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Attach the data source to the collection node. + collectionNode.dataSource = dataSource; + collectionNode.delegate = dataSource; + __weak IGListAdapter *weakSelf = self; + [collectionNode onDidLoad:^(__kindof ASCollectionNode * _Nonnull collectionNode) { +#if IG_LIST_COLLECTION_VIEW + // We manually set the superclass of ASCollectionView to IGListCollectionView at runtime if needed. + weakSelf.collectionView = (IGListCollectionView *)collectionNode.view; +#else + weakSelf.collectionView = collectionNode.view; +#endif + }]; +} + +@end + +#endif // AS_IG_LIST_KIT diff --git a/AsyncDisplayKit-iOS/Info.plist b/Source/Info.plist similarity index 93% rename from AsyncDisplayKit-iOS/Info.plist rename to Source/Info.plist index d3de8eefb6..fbe1e6b314 100644 --- a/AsyncDisplayKit-iOS/Info.plist +++ b/Source/Info.plist @@ -16,8 +16,6 @@ FMWK CFBundleShortVersionString 1.0 - CFBundleSignature - ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/AsyncDisplayKit/Layout/ASAbsoluteLayoutElement.h b/Source/Layout/ASAbsoluteLayoutElement.h similarity index 94% rename from AsyncDisplayKit/Layout/ASAbsoluteLayoutElement.h rename to Source/Layout/ASAbsoluteLayoutElement.h index 8d97647132..2c222ae1af 100644 --- a/AsyncDisplayKit/Layout/ASAbsoluteLayoutElement.h +++ b/Source/Layout/ASAbsoluteLayoutElement.h @@ -9,7 +9,7 @@ // #import -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/Layout/ASAbsoluteLayoutSpec.h b/Source/Layout/ASAbsoluteLayoutSpec.h similarity index 100% rename from AsyncDisplayKit/Layout/ASAbsoluteLayoutSpec.h rename to Source/Layout/ASAbsoluteLayoutSpec.h diff --git a/AsyncDisplayKit/Layout/ASAbsoluteLayoutSpec.mm b/Source/Layout/ASAbsoluteLayoutSpec.mm similarity index 93% rename from AsyncDisplayKit/Layout/ASAbsoluteLayoutSpec.mm rename to Source/Layout/ASAbsoluteLayoutSpec.mm index 8c4ff3d2b5..920a009376 100644 --- a/AsyncDisplayKit/Layout/ASAbsoluteLayoutSpec.mm +++ b/Source/Layout/ASAbsoluteLayoutSpec.mm @@ -8,12 +8,12 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASAbsoluteLayoutSpec.h" +#import -#import "ASLayout.h" -#import "ASLayoutSpec+Subclasses.h" -#import "ASLayoutSpecUtilities.h" -#import "ASLayoutElementStylePrivate.h" +#import +#import +#import +#import #pragma mark - ASAbsoluteLayoutSpec diff --git a/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.h b/Source/Layout/ASAsciiArtBoxCreator.h similarity index 98% rename from AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.h rename to Source/Layout/ASAsciiArtBoxCreator.h index 76c7f41b66..d032751518 100644 --- a/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.h +++ b/Source/Layout/ASAsciiArtBoxCreator.h @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m b/Source/Layout/ASAsciiArtBoxCreator.m similarity index 98% rename from AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m rename to Source/Layout/ASAsciiArtBoxCreator.m index 98f335ef1e..03d49eb02c 100644 --- a/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m +++ b/Source/Layout/ASAsciiArtBoxCreator.m @@ -8,8 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASAsciiArtBoxCreator.h" +#import +#import #import static const NSUInteger kDebugBoxPadding = 2; diff --git a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h b/Source/Layout/ASBackgroundLayoutSpec.h similarity index 81% rename from AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h rename to Source/Layout/ASBackgroundLayoutSpec.h index ff9a857d82..dbdf79b9ca 100644 --- a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h +++ b/Source/Layout/ASBackgroundLayoutSpec.h @@ -20,15 +20,15 @@ NS_ASSUME_NONNULL_BEGIN /** * Background layoutElement for this layout spec */ -@property (nullable, nonatomic, strong) id background; +@property (nonatomic, strong) id background; /** * Creates and returns an ASBackgroundLayoutSpec object * * @param child A child that is laid out to determine the size of this spec. - * @param background A layoutElement object that is laid out behind the child. If this is nil, the background is omitted. + * @param background A layoutElement object that is laid out behind the child. */ -+ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(nullable id)background AS_WARN_UNUSED_RESULT; ++ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background AS_WARN_UNUSED_RESULT; @end diff --git a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm b/Source/Layout/ASBackgroundLayoutSpec.mm similarity index 80% rename from AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm rename to Source/Layout/ASBackgroundLayoutSpec.mm index a94c051a6b..a3fb85e078 100644 --- a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm +++ b/Source/Layout/ASBackgroundLayoutSpec.mm @@ -8,9 +8,11 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASBackgroundLayoutSpec.h" -#import "ASLayoutSpec+Subclasses.h" -#import "ASAssert.h" +#import + +#import + +#import static NSUInteger const kForegroundChildIndex = 0; static NSUInteger const kBackgroundChildIndex = 1; @@ -31,9 +33,7 @@ - (instancetype)initWithChild:(id)child background:(id)child +{ + ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); + [super setChild:child atIndex:kForegroundChildIndex]; +} + +- (id)child +{ + return [super childAtIndex:kForegroundChildIndex]; +} + - (void)setBackground:(id)background { + ASDisplayNodeAssertNotNil(background, @"Background cannot be nil"); [super setChild:background atIndex:kBackgroundChildIndex]; } diff --git a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h b/Source/Layout/ASCenterLayoutSpec.h similarity index 87% rename from AsyncDisplayKit/Layout/ASCenterLayoutSpec.h rename to Source/Layout/ASCenterLayoutSpec.h index a1474c4840..4d995fcb8d 100644 --- a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h +++ b/Source/Layout/ASCenterLayoutSpec.h @@ -10,7 +10,12 @@ #import -/** How the child is centered within the spec. */ +/** + * How the child is centered within the spec. + * + * The default option will position the child at {0,0} relatively to the layout bound. + * Swift: use [] for the default behavior. + */ typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecCenteringOptions) { /** The child is positioned in {0,0} relatively to the layout bounds */ ASCenterLayoutSpecCenteringNone = 0, @@ -22,7 +27,12 @@ typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecCenteringOptions) { ASCenterLayoutSpecCenteringXY = ASCenterLayoutSpecCenteringX | ASCenterLayoutSpecCenteringY }; -/** How much space the spec will take up. */ +/** + * How much space the spec will take up. + * + * The default option will allow the spec to take up the maximum size possible. + * Swift: use [] for the default behavior. + */ typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecSizingOptions) { /** The spec will take up the maximum size possible */ ASCenterLayoutSpecSizingOptionDefault = ASRelativeLayoutSpecSizingOptionDefault, diff --git a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm b/Source/Layout/ASCenterLayoutSpec.mm similarity index 97% rename from AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm rename to Source/Layout/ASCenterLayoutSpec.mm index cbd06bb8f9..f4715e422d 100644 --- a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm +++ b/Source/Layout/ASCenterLayoutSpec.mm @@ -8,9 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASCenterLayoutSpec.h" +#import -#import "ASLayout.h" +#import @implementation ASCenterLayoutSpec { diff --git a/AsyncDisplayKit/Layout/ASDimension.h b/Source/Layout/ASDimension.h similarity index 52% rename from AsyncDisplayKit/Layout/ASDimension.h rename to Source/Layout/ASDimension.h index 80d9e542bc..cb14eef003 100644 --- a/AsyncDisplayKit/Layout/ASDimension.h +++ b/Source/Layout/ASDimension.h @@ -9,10 +9,16 @@ // #pragma once -#import +#import +#import #import #import +ASDISPLAYNODE_EXTERN_C_BEGIN +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - + ASDISPLAYNODE_INLINE BOOL AS_WARN_UNUSED_RESULT ASPointsValidForLayout(CGFloat points) { return ((isnormal(points) || points == 0.0) && points >= 0.0 && points < (CGFLOAT_MAX / 2.0)); @@ -20,19 +26,36 @@ ASDISPLAYNODE_INLINE BOOL AS_WARN_UNUSED_RESULT ASPointsValidForLayout(CGFloat p ASDISPLAYNODE_INLINE BOOL AS_WARN_UNUSED_RESULT ASIsCGSizeValidForLayout(CGSize size) { - return (ASPointsValidForLayout(size.width) && ASPointsValidForLayout(size.height)); + return (ASPointsValidForLayout(size.width) && ASPointsValidForLayout(size.height)); } ASDISPLAYNODE_INLINE BOOL AS_WARN_UNUSED_RESULT ASPointsValidForSize(CGFloat points) { - return ((isnormal(points) || points == 0.0) && points >= 0.0 && points < (FLT_MAX / 2.0)); + return ((isnormal(points) || points == 0.0) && points >= 0.0 && points < (FLT_MAX / 2.0)); } ASDISPLAYNODE_INLINE BOOL AS_WARN_UNUSED_RESULT ASIsCGSizeValidForSize(CGSize size) { - return (ASPointsValidForSize(size.width) && ASPointsValidForSize(size.height)); + return (ASPointsValidForSize(size.width) && ASPointsValidForSize(size.height)); +} + +ASDISPLAYNODE_INLINE BOOL ASIsCGPositionPointsValidForLayout(CGFloat points) +{ + return ((isnormal(points) || points == 0.0) && points < (CGFLOAT_MAX / 2.0)); +} + +ASDISPLAYNODE_INLINE BOOL ASIsCGPositionValidForLayout(CGPoint point) +{ + return (ASIsCGPositionPointsValidForLayout(point.x) && ASIsCGPositionPointsValidForLayout(point.y)); } +ASDISPLAYNODE_INLINE BOOL ASIsCGRectValidForLayout(CGRect rect) +{ + return (ASIsCGPositionValidForLayout(rect.origin) && ASIsCGSizeValidForLayout(rect.size)); +} + +#pragma mark - ASDimension + /** * A dimension relative to constraints to be provided in the future. * A ASDimension can be one of three types: @@ -58,42 +81,10 @@ typedef struct { } ASDimension; /** - * Expresses an inclusive range of sizes. Used to provide a simple constraint to layout. + * Represents auto as ASDimension */ -typedef struct { - CGSize min; - CGSize max; -} ASSizeRange; - -/** - * A struct specifying a ASLayoutElement's size. Example: - * - * ASLayoutElementSize size = (ASLayoutElementSize){ - * .width = ASDimensionMakeWithFraction(0.25), - * .maxWidth = ASDimensionMakeWithPoints(200), - * .minHeight = ASDimensionMakeWithFraction(0.50) - * }; - * - * Description: - * - */ -typedef struct { - ASDimension width; - ASDimension height; - ASDimension minWidth; - ASDimension maxWidth; - ASDimension minHeight; - ASDimension maxHeight; -} ASLayoutElementSize; - extern ASDimension const ASDimensionAuto; -ASDISPLAYNODE_EXTERN_C_BEGIN -NS_ASSUME_NONNULL_BEGIN - - -#pragma mark - ASDimension - /** * Returns a dimension with the specified type and value. */ @@ -173,15 +164,6 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT CGFloat ASDimensionResolve(ASDimensio } } - -#pragma mark - NSNumber+ASDimension - -@interface NSNumber (ASDimension) -@property (nonatomic, readonly) ASDimension as_pointDimension; -@property (nonatomic, readonly) ASDimension as_fractionDimension; -@end - - #pragma mark - ASLayoutSize /** @@ -205,6 +187,15 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASLayoutSize ASLayoutSizeMake(ASDimen return size; } +/** + * Resolve this relative size relative to a parent size. + */ +ASDISPLAYNODE_INLINE CGSize ASLayoutSizeResolveSize(ASLayoutSize layoutSize, CGSize parentSize, CGSize autoSize) +{ + return CGSizeMake(ASDimensionResolve(layoutSize.width, parentSize.width, autoSize.width), + ASDimensionResolve(layoutSize.height, parentSize.height, autoSize.height)); +} + /* * Returns a string representation of a relative size. */ @@ -215,8 +206,46 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT NSString *NSStringFromASLayoutSize(AS NSStringFromASDimension(size.height)]; } +#pragma mark - ASEdgeInsets + +typedef struct { + ASDimension top; + ASDimension left; + ASDimension bottom; + ASDimension right; +} ASEdgeInsets; + +extern ASEdgeInsets const ASEdgeInsetsZero; + #pragma mark - ASSizeRange +/** + * Expresses an inclusive range of sizes. Used to provide a simple constraint to layout. + */ +typedef struct { + CGSize min; + CGSize max; +} ASSizeRange; + +/** + * A size range with all dimensions zero. + */ +extern ASSizeRange const ASSizeRangeZero; + +/** + * A size range from zero to infinity in both directions. + */ +extern ASSizeRange const ASSizeRangeUnconstrained; + +/** + * Returns whether a size range has > 0.1 max width and max height. + */ +ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASSizeRangeHasSignificantArea(ASSizeRange sizeRange) +{ + static CGFloat const limit = 0.1; + return (sizeRange.max.width > limit && sizeRange.max.height > limit); +} + /** * Creates an ASSizeRange with provided min and max size. */ @@ -272,149 +301,5 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASSizeRangeEqualToSizeRange(ASSi */ extern AS_WARN_UNUSED_RESULT NSString *NSStringFromASSizeRange(ASSizeRange sizeRange); - -#pragma mark - ASLayoutElementSize - -/** - * Returns an ASLayoutElementSize with default values. - */ -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASLayoutElementSize ASLayoutElementSizeMake() -{ - return (ASLayoutElementSize){ - .width = ASDimensionAuto, - .height = ASDimensionAuto, - .minWidth = ASDimensionAuto, - .maxWidth = ASDimensionAuto, - .minHeight = ASDimensionAuto, - .maxHeight = ASDimensionAuto - }; -} - -/** - * Returns an ASLayoutElementSize with the specified CGSize values as width and height. - */ -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASLayoutElementSize ASLayoutElementSizeMakeFromCGSize(CGSize size) -{ - ASLayoutElementSize s = ASLayoutElementSizeMake(); - s.width = ASDimensionMakeWithPoints(size.width); - s.height = ASDimensionMakeWithPoints(size.height); - return s; -} - -/** - * Returns whether two sizes are equal. - */ -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASLayoutElementSizeEqualToLayoutElementSize(ASLayoutElementSize lhs, ASLayoutElementSize rhs) -{ - return (ASDimensionEqualToDimension(lhs.width, rhs.width) - && ASDimensionEqualToDimension(lhs.height, rhs.height) - && ASDimensionEqualToDimension(lhs.minWidth, rhs.minWidth) - && ASDimensionEqualToDimension(lhs.maxWidth, rhs.maxWidth) - && ASDimensionEqualToDimension(lhs.minHeight, rhs.minHeight) - && ASDimensionEqualToDimension(lhs.maxHeight, rhs.maxHeight)); -} - -/** - * Returns a string formatted to contain the data from an ASLayoutElementSize. - */ -extern AS_WARN_UNUSED_RESULT NSString *NSStringFromASLayoutElementSize(ASLayoutElementSize size); - -/** - * Resolve the given size relative to a parent size and an auto size. - * From the given size uses width, height to resolve the exact size constraint, uses the minHeight and minWidth to - * resolve the min size constraint and the maxHeight and maxWidth to resolve the max size constraint. For every - * dimension with unit ASDimensionUnitAuto the given autoASSizeRange value will be used. - * Based on the calculated exact, min and max size constraints the final size range will be calculated. - */ -extern AS_WARN_UNUSED_RESULT ASSizeRange ASLayoutElementSizeResolveAutoSize(ASLayoutElementSize size, const CGSize parentSize, ASSizeRange autoASSizeRange); - -/** - * Resolve the given size to a parent size. Uses internally ASLayoutElementSizeResolveAutoSize with {INFINITY, INFINITY} as - * as autoASSizeRange. For more information look at ASLayoutElementSizeResolveAutoSize. - */ -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASLayoutElementSizeResolve(ASLayoutElementSize size, const CGSize parentSize) -{ - return ASLayoutElementSizeResolveAutoSize(size, parentSize, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); -} - - -#pragma mark - Deprecated - -/** - * A dimension relative to constraints to be provided in the future. - * A ASDimension can be one of three types: - * - * "Auto" - This indicated "I have no opinion" and may be resolved in whatever way makes most sense given the circumstances. - * - * "Points" - Just a number. It will always resolve to exactly this amount. - * - * "Percent" - Multiplied to a provided parent amount to resolve a final amount. - */ -typedef NS_ENUM(NSInteger, ASRelativeDimensionType) { - /** This indicates "I have no opinion" and may be resolved in whatever way makes most sense given the circumstances. */ - ASRelativeDimensionTypeAuto, - /** Just a number. It will always resolve to exactly this amount. This is the default type. */ - ASRelativeDimensionTypePoints, - /** Multiplied to a provided parent amount to resolve a final amount. */ - ASRelativeDimensionTypeFraction, -}; - -#define ASRelativeDimension ASDimension -#define ASRelativeSize ASLayoutSize -#define ASRelativeDimensionMakeWithPoints ASDimensionMakeWithPoints -#define ASRelativeDimensionMakeWithFraction ASDimensionMakeWithFraction - -/** - * Function is deprecated. Use ASSizeRangeMake instead. - */ -extern AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMakeExactSize(CGSize size) ASDISPLAYNODE_DEPRECATED_MSG("Use ASSizeRangeMake instead."); - -/** - Expresses an inclusive range of relative sizes. Used to provide additional constraint to layout. - Used by ASStaticLayoutSpec. - */ -typedef struct { - ASLayoutSize min; - ASLayoutSize max; -} ASRelativeSizeRange; - -extern ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained; - -#pragma mark - ASRelativeDimension - -extern ASDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) ASDISPLAYNODE_DEPRECATED; - -#pragma mark - ASRelativeSize - -extern ASLayoutSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) ASDISPLAYNODE_DEPRECATED; - -/** Convenience constructor to provide size in points. */ -extern ASLayoutSize ASRelativeSizeMakeWithCGSize(CGSize size) ASDISPLAYNODE_DEPRECATED; - -/** Convenience constructor to provide size as a fraction. */ -extern ASLayoutSize ASRelativeSizeMakeWithFraction(CGFloat fraction) ASDISPLAYNODE_DEPRECATED; - -extern BOOL ASRelativeSizeEqualToRelativeSize(ASLayoutSize lhs, ASLayoutSize rhs) ASDISPLAYNODE_DEPRECATED; - -extern NSString *NSStringFromASRelativeSize(ASLayoutSize size) ASDISPLAYNODE_DEPRECATED; - -#pragma mark - ASRelativeSizeRange - -extern ASRelativeSizeRange ASRelativeSizeRangeMake(ASLayoutSize min, ASLayoutSize max) ASDISPLAYNODE_DEPRECATED; - -#pragma mark Convenience constructors to provide an exact size (min == max). -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASLayoutSize exact) ASDISPLAYNODE_DEPRECATED; - -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) ASDISPLAYNODE_DEPRECATED; - -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction) ASDISPLAYNODE_DEPRECATED; - -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight) ASDISPLAYNODE_DEPRECATED; - -extern BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs) ASDISPLAYNODE_DEPRECATED; - -/** Provided a parent size, compute final dimensions for this RelativeSizeRange to arrive at a SizeRange. */ -extern ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, CGSize parentSize) ASDISPLAYNODE_DEPRECATED; - NS_ASSUME_NONNULL_END ASDISPLAYNODE_EXTERN_C_END diff --git a/Source/Layout/ASDimension.mm b/Source/Layout/ASDimension.mm new file mode 100644 index 0000000000..86a5270d14 --- /dev/null +++ b/Source/Layout/ASDimension.mm @@ -0,0 +1,110 @@ +// +// ASDimension.mm +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import + +#import + +#import + +#pragma mark - ASDimension + +ASDimension const ASDimensionAuto = {ASDimensionUnitAuto, 0}; + +ASOVERLOADABLE ASDimension ASDimensionMake(NSString *dimension) +{ + if (dimension.length > 0) { + + // Handle points + if ([dimension hasSuffix:@"pt"]) { + return ASDimensionMake(ASDimensionUnitPoints, ASCGFloatFromString(dimension)); + } + + // Handle auto + if ([dimension isEqualToString:@"auto"]) { + return ASDimensionAuto; + } + + // Handle percent + if ([dimension hasSuffix:@"%"]) { + return ASDimensionMake(ASDimensionUnitFraction, (ASCGFloatFromString(dimension) / 100.0)); + } + } + + ASDisplayNodeCAssert(NO, @"Parsing dimension failed for: %@", dimension); + return ASDimensionAuto; +} + +NSString *NSStringFromASDimension(ASDimension dimension) +{ + switch (dimension.unit) { + case ASDimensionUnitPoints: + return [NSString stringWithFormat:@"%.0fpt", dimension.value]; + case ASDimensionUnitFraction: + return [NSString stringWithFormat:@"%.0f%%", dimension.value * 100.0]; + case ASDimensionUnitAuto: + return @"Auto"; + } +} + +#pragma mark - ASLayoutSize + +ASLayoutSize const ASLayoutSizeAuto = {ASDimensionAuto, ASDimensionAuto}; + +#pragma mark - ASEdgeInsets + +ASEdgeInsets const ASEdgeInsetsZero = {}; + +#pragma mark - ASSizeRange + +ASSizeRange const ASSizeRangeZero = {}; + +ASSizeRange const ASSizeRangeUnconstrained = { {0, 0}, { INFINITY, INFINITY }}; + +struct _Range { + CGFloat min; + CGFloat max; + + /** + Intersects another dimension range. If the other range does not overlap, this size range "wins" by returning a + single point within its own range that is closest to the non-overlapping range. + */ + _Range intersect(const _Range &other) const + { + CGFloat newMin = MAX(min, other.min); + CGFloat newMax = MIN(max, other.max); + if (newMin <= newMax) { + return {newMin, newMax}; + } else { + // No intersection. If we're before the other range, return our max; otherwise our min. + if (min < other.min) { + return {max, max}; + } else { + return {min, min}; + } + } + } +}; + +ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRange) +{ + auto w = _Range({sizeRange.min.width, sizeRange.max.width}).intersect({otherSizeRange.min.width, otherSizeRange.max.width}); + auto h = _Range({sizeRange.min.height, sizeRange.max.height}).intersect({otherSizeRange.min.height, otherSizeRange.max.height}); + return {{w.min, h.min}, {w.max, h.max}}; +} + +NSString *NSStringFromASSizeRange(ASSizeRange sizeRange) +{ + return [NSString stringWithFormat:@"", + NSStringFromCGSize(sizeRange.min), + NSStringFromCGSize(sizeRange.max)]; +} diff --git a/Source/Layout/ASDimensionDeprecated.h b/Source/Layout/ASDimensionDeprecated.h new file mode 100644 index 0000000000..7ddd438dbd --- /dev/null +++ b/Source/Layout/ASDimensionDeprecated.h @@ -0,0 +1,95 @@ +// +// ASDimensionDeprecated.h +// AsyncDisplayKit +// +// 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. +// + +#pragma once +#import +#import + +ASDISPLAYNODE_EXTERN_C_BEGIN +NS_ASSUME_NONNULL_BEGIN + +/** + * A dimension relative to constraints to be provided in the future. + * A ASDimension can be one of three types: + * + * "Auto" - This indicated "I have no opinion" and may be resolved in whatever way makes most sense given the circumstances. + * + * "Points" - Just a number. It will always resolve to exactly this amount. + * + * "Percent" - Multiplied to a provided parent amount to resolve a final amount. + */ +typedef NS_ENUM(NSInteger, ASRelativeDimensionType) { + /** This indicates "I have no opinion" and may be resolved in whatever way makes most sense given the circumstances. */ + ASRelativeDimensionTypeAuto, + /** Just a number. It will always resolve to exactly this amount. This is the default type. */ + ASRelativeDimensionTypePoints, + /** Multiplied to a provided parent amount to resolve a final amount. */ + ASRelativeDimensionTypeFraction, +}; + +#define ASRelativeDimension ASDimension +#define ASRelativeSize ASLayoutSize +#define ASRelativeDimensionMakeWithPoints ASDimensionMakeWithPoints +#define ASRelativeDimensionMakeWithFraction ASDimensionMakeWithFraction + +/** + * Function is deprecated. Use ASSizeRangeMake instead. + */ +extern AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMakeExactSize(CGSize size) ASDISPLAYNODE_DEPRECATED_MSG("Use ASSizeRangeMake instead."); + +/** + Expresses an inclusive range of relative sizes. Used to provide additional constraint to layout. + Used by ASStaticLayoutSpec. + */ +typedef struct { + ASLayoutSize min; + ASLayoutSize max; +} ASRelativeSizeRange; + +extern ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained; + +#pragma mark - ASRelativeDimension + +extern ASDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) ASDISPLAYNODE_DEPRECATED; + +#pragma mark - ASRelativeSize + +extern ASLayoutSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) ASDISPLAYNODE_DEPRECATED; + +/** Convenience constructor to provide size in points. */ +extern ASLayoutSize ASRelativeSizeMakeWithCGSize(CGSize size) ASDISPLAYNODE_DEPRECATED; + +/** Convenience constructor to provide size as a fraction. */ +extern ASLayoutSize ASRelativeSizeMakeWithFraction(CGFloat fraction) ASDISPLAYNODE_DEPRECATED; + +extern BOOL ASRelativeSizeEqualToRelativeSize(ASLayoutSize lhs, ASLayoutSize rhs) ASDISPLAYNODE_DEPRECATED; + +extern NSString *NSStringFromASRelativeSize(ASLayoutSize size) ASDISPLAYNODE_DEPRECATED; + +#pragma mark - ASRelativeSizeRange + +extern ASRelativeSizeRange ASRelativeSizeRangeMake(ASLayoutSize min, ASLayoutSize max) ASDISPLAYNODE_DEPRECATED; + +#pragma mark Convenience constructors to provide an exact size (min == max). +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASLayoutSize exact) ASDISPLAYNODE_DEPRECATED; + +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) ASDISPLAYNODE_DEPRECATED; + +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction) ASDISPLAYNODE_DEPRECATED; + +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight) ASDISPLAYNODE_DEPRECATED; + +extern BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs) ASDISPLAYNODE_DEPRECATED; + +/** Provided a parent size, compute final dimensions for this RelativeSizeRange to arrive at a SizeRange. */ +extern ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, CGSize parentSize) ASDISPLAYNODE_DEPRECATED; + +NS_ASSUME_NONNULL_END +ASDISPLAYNODE_EXTERN_C_END diff --git a/Source/Layout/ASDimensionDeprecated.mm b/Source/Layout/ASDimensionDeprecated.mm new file mode 100644 index 0000000000..328b24110e --- /dev/null +++ b/Source/Layout/ASDimensionDeprecated.mm @@ -0,0 +1,95 @@ +// +// ASDimensionDeprecated.mm +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +ASDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) +{ + if (type == ASRelativeDimensionTypePoints) { + return ASDimensionMakeWithPoints(value); + } else if (type == ASRelativeDimensionTypeFraction) { + return ASDimensionMakeWithFraction(value); + } + + ASDisplayNodeCAssert(NO, @"ASRelativeDimensionMake does not support the given ASRelativeDimensionType"); + return ASDimensionMakeWithPoints(0); +} + +ASSizeRange ASSizeRangeMakeExactSize(CGSize size) +{ + return ASSizeRangeMake(size); +} + +ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained = {}; + +#pragma mark - ASRelativeSize + +ASLayoutSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) +{ + return ASLayoutSizeMake(width, height); +} + +ASLayoutSize ASRelativeSizeMakeWithCGSize(CGSize size) +{ + return ASRelativeSizeMake(ASRelativeDimensionMakeWithPoints(size.width), + ASRelativeDimensionMakeWithPoints(size.height)); +} + +ASLayoutSize ASRelativeSizeMakeWithFraction(CGFloat fraction) +{ + return ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(fraction), + ASRelativeDimensionMakeWithFraction(fraction)); +} + +BOOL ASRelativeSizeEqualToRelativeSize(ASLayoutSize lhs, ASLayoutSize rhs) +{ + return ASDimensionEqualToDimension(lhs.width, rhs.width) + && ASDimensionEqualToDimension(lhs.height, rhs.height); +} + + +#pragma mark - ASRelativeSizeRange + +ASRelativeSizeRange ASRelativeSizeRangeMake(ASLayoutSize min, ASLayoutSize max) +{ + ASRelativeSizeRange sizeRange; sizeRange.min = min; sizeRange.max = max; return sizeRange; +} + +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASLayoutSize exact) +{ + return ASRelativeSizeRangeMake(exact, exact); +} + +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) +{ + return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithCGSize(exact)); +} + +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction) +{ + return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithFraction(fraction)); +} + +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight) +{ + return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMake(exactWidth, exactHeight)); +} + +BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs) +{ + return ASRelativeSizeEqualToRelativeSize(lhs.min, rhs.min) && ASRelativeSizeEqualToRelativeSize(lhs.max, rhs.max); +} + +ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, + CGSize parentSize) +{ + return ASSizeRangeMake(ASLayoutSizeResolveSize(relativeSizeRange.min, parentSize, parentSize), + ASLayoutSizeResolveSize(relativeSizeRange.max, parentSize, parentSize)); +} diff --git a/Source/Layout/ASDimensionInternal.h b/Source/Layout/ASDimensionInternal.h new file mode 100644 index 0000000000..e4ef18ec5d --- /dev/null +++ b/Source/Layout/ASDimensionInternal.h @@ -0,0 +1,105 @@ +// +// ASDimensionInternal.h +// AsyncDisplayKit +// +// 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. +// + +#pragma once +#import +#import + +ASDISPLAYNODE_EXTERN_C_BEGIN +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - ASLayoutElementSize + +/** + * A struct specifying a ASLayoutElement's size. Example: + * + * ASLayoutElementSize size = (ASLayoutElementSize){ + * .width = ASDimensionMakeWithFraction(0.25), + * .maxWidth = ASDimensionMakeWithPoints(200), + * .minHeight = ASDimensionMakeWithFraction(0.50) + * }; + * + * Description: + * + */ +typedef struct { + ASDimension width; + ASDimension height; + ASDimension minWidth; + ASDimension maxWidth; + ASDimension minHeight; + ASDimension maxHeight; +} ASLayoutElementSize; + +/** + * Returns an ASLayoutElementSize with default values. + */ +ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASLayoutElementSize ASLayoutElementSizeMake() +{ + return (ASLayoutElementSize){ + .width = ASDimensionAuto, + .height = ASDimensionAuto, + .minWidth = ASDimensionAuto, + .maxWidth = ASDimensionAuto, + .minHeight = ASDimensionAuto, + .maxHeight = ASDimensionAuto + }; +} + +/** + * Returns an ASLayoutElementSize with the specified CGSize values as width and height. + */ +ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASLayoutElementSize ASLayoutElementSizeMakeFromCGSize(CGSize size) +{ + ASLayoutElementSize s = ASLayoutElementSizeMake(); + s.width = ASDimensionMakeWithPoints(size.width); + s.height = ASDimensionMakeWithPoints(size.height); + return s; +} + +/** + * Returns whether two sizes are equal. + */ +ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASLayoutElementSizeEqualToLayoutElementSize(ASLayoutElementSize lhs, ASLayoutElementSize rhs) +{ + return (ASDimensionEqualToDimension(lhs.width, rhs.width) + && ASDimensionEqualToDimension(lhs.height, rhs.height) + && ASDimensionEqualToDimension(lhs.minWidth, rhs.minWidth) + && ASDimensionEqualToDimension(lhs.maxWidth, rhs.maxWidth) + && ASDimensionEqualToDimension(lhs.minHeight, rhs.minHeight) + && ASDimensionEqualToDimension(lhs.maxHeight, rhs.maxHeight)); +} + +/** + * Returns a string formatted to contain the data from an ASLayoutElementSize. + */ +extern AS_WARN_UNUSED_RESULT NSString *NSStringFromASLayoutElementSize(ASLayoutElementSize size); + +/** + * Resolve the given size relative to a parent size and an auto size. + * From the given size uses width, height to resolve the exact size constraint, uses the minHeight and minWidth to + * resolve the min size constraint and the maxHeight and maxWidth to resolve the max size constraint. For every + * dimension with unit ASDimensionUnitAuto the given autoASSizeRange value will be used. + * Based on the calculated exact, min and max size constraints the final size range will be calculated. + */ +extern AS_WARN_UNUSED_RESULT ASSizeRange ASLayoutElementSizeResolveAutoSize(ASLayoutElementSize size, const CGSize parentSize, ASSizeRange autoASSizeRange); + +/** + * Resolve the given size to a parent size. Uses internally ASLayoutElementSizeResolveAutoSize with {INFINITY, INFINITY} as + * as autoASSizeRange. For more information look at ASLayoutElementSizeResolveAutoSize. + */ +ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASLayoutElementSizeResolve(ASLayoutElementSize size, const CGSize parentSize) +{ + return ASLayoutElementSizeResolveAutoSize(size, parentSize, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); +} + + +NS_ASSUME_NONNULL_END +ASDISPLAYNODE_EXTERN_C_END diff --git a/Source/Layout/ASDimensionInternal.mm b/Source/Layout/ASDimensionInternal.mm new file mode 100644 index 0000000000..b2c1013f2b --- /dev/null +++ b/Source/Layout/ASDimensionInternal.mm @@ -0,0 +1,66 @@ +// +// ASDimensionInternal.mm +// AsyncDisplayKit +// +// 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 + +#pragma mark - ASLayoutElementSize + +NSString *NSStringFromASLayoutElementSize(ASLayoutElementSize size) +{ + return [NSString stringWithFormat: + @"", + NSStringFromASLayoutSize(ASLayoutSizeMake(size.width, size.height)), + NSStringFromASLayoutSize(ASLayoutSizeMake(size.minWidth, size.minHeight)), + NSStringFromASLayoutSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight))]; +} + +ASDISPLAYNODE_INLINE void ASLayoutElementSizeConstrain(CGFloat minVal, CGFloat exactVal, CGFloat maxVal, CGFloat *outMin, CGFloat *outMax) +{ + NSCAssert(!isnan(minVal), @"minVal must not be NaN"); + NSCAssert(!isnan(maxVal), @"maxVal must not be NaN"); + // Avoid use of min/max primitives since they're harder to reason + // about in the presence of NaN (in exactVal) + // Follow CSS: min overrides max overrides exact. + + // Begin with the min/max range + *outMin = minVal; + *outMax = maxVal; + if (maxVal <= minVal) { + // min overrides max and exactVal is irrelevant + *outMax = minVal; + return; + } + if (isnan(exactVal)) { + // no exact value, so leave as a min/max range + return; + } + if (exactVal > maxVal) { + // clip to max value + *outMin = maxVal; + } else if (exactVal < minVal) { + // clip to min value + *outMax = minVal; + } else { + // use exact value + *outMin = *outMax = exactVal; + } +} + +ASSizeRange ASLayoutElementSizeResolveAutoSize(ASLayoutElementSize size, const CGSize parentSize, ASSizeRange autoASSizeRange) +{ + CGSize resolvedExact = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.width, size.height), parentSize, {NAN, NAN}); + CGSize resolvedMin = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.minWidth, size.minHeight), parentSize, autoASSizeRange.min); + CGSize resolvedMax = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight), parentSize, autoASSizeRange.max); + + CGSize rangeMin, rangeMax; + ASLayoutElementSizeConstrain(resolvedMin.width, resolvedExact.width, resolvedMax.width, &rangeMin.width, &rangeMax.width); + ASLayoutElementSizeConstrain(resolvedMin.height, resolvedExact.height, resolvedMax.height, &rangeMin.height, &rangeMax.height); + return {rangeMin, rangeMax}; +} diff --git a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h b/Source/Layout/ASInsetLayoutSpec.h similarity index 100% rename from AsyncDisplayKit/Layout/ASInsetLayoutSpec.h rename to Source/Layout/ASInsetLayoutSpec.h diff --git a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm b/Source/Layout/ASInsetLayoutSpec.mm similarity index 95% rename from AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm rename to Source/Layout/ASInsetLayoutSpec.mm index 156975a17d..7c4bc92f1a 100644 --- a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm +++ b/Source/Layout/ASInsetLayoutSpec.mm @@ -8,10 +8,12 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASInsetLayoutSpec.h" -#import "ASAssert.h" -#import "ASLayoutSpec+Subclasses.h" -#import "ASInternalHelpers.h" +#import + +#import + +#import +#import @interface ASInsetLayoutSpec () { diff --git a/AsyncDisplayKit/Layout/ASLayout.h b/Source/Layout/ASLayout.h similarity index 90% rename from AsyncDisplayKit/Layout/ASLayout.h rename to Source/Layout/ASLayout.h index 76389398f7..b592ddb13b 100644 --- a/AsyncDisplayKit/Layout/ASLayout.h +++ b/Source/Layout/ASLayout.h @@ -9,11 +9,9 @@ // #pragma once - -#import -#import -#import +#import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -32,11 +30,11 @@ extern ASLayout *ASCalculateRootLayout(id rootLayoutElement, co /** * Safely computes the layout of the given node by guarding against nil nodes. - * @param component The component to calculate the layout for. + * @param layoutElement The layout element to calculate the layout for. * @param sizeRange The size range to calculate the node layout within. * @param parentSize The parent size of the node to calculate the layout for. */ -extern ASLayout *ASCalculateLayout(id layoutElement, const ASSizeRange sizeRange, const CGSize parentSize); +extern ASLayout *ASCalculateLayout(idlayoutElement, const ASSizeRange sizeRange, const CGSize parentSize); ASDISPLAYNODE_EXTERN_C_END @@ -72,6 +70,12 @@ ASDISPLAYNODE_EXTERN_C_END */ @property (nonatomic, copy, readonly) NSArray *sublayouts; +/** + * The frame for the given element, or CGRectNull if + * the element is not a direct descendent of this layout. + */ +- (CGRect)frameForElement:(id)layoutElement; + /** * @abstract Returns a valid frame for the current layout computed with the size and position. * @discussion Clamps the layout's origin or position to 0 if any of the calculated values are infinite. @@ -82,9 +86,9 @@ ASDISPLAYNODE_EXTERN_C_END * Designated initializer */ - (instancetype)initWithLayoutElement:(id)layoutElement - size:(CGSize)size - position:(CGPoint)position - sublayouts:(nullable NSArray *)sublayouts NS_DESIGNATED_INITIALIZER; + size:(CGSize)size + position:(CGPoint)position + sublayouts:(nullable NSArray *)sublayouts NS_DESIGNATED_INITIALIZER; /** * Convenience class initializer for layout construction. diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm similarity index 79% rename from AsyncDisplayKit/Layout/ASLayout.mm rename to Source/Layout/ASLayout.mm index a64d81be2e..aaa569ee4a 100644 --- a/AsyncDisplayKit/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -8,13 +8,17 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDimension.h" -#import "ASInternalHelpers.h" -#import "ASLayoutSpecUtilities.h" -#import "ASLayoutSpec+Subclasses.h" +#import #import -#import "ASObjectDescriptionHelpers.h" + +#import +#import +#import + +#import +#import +#import CGPoint const CGPointNull = {NAN, NAN}; @@ -47,6 +51,18 @@ @interface ASLayout () */ @property (nonatomic, getter=isFlattened) BOOL flattened; +/* + * Caches all sublayouts if set to YES or destroys the sublayout cache if set to NO. Defaults to YES + */ +@property (nonatomic, assign) BOOL retainSublayoutLayoutElements; + +/** + * Array for explicitly retain sublayout layout elements in case they are created and references in layoutSpecThatFits: and no one else will hold a strong reference on it + */ +@property (nonatomic, strong) NSMutableArray> *sublayoutLayoutElements; + +@property (nonatomic, strong, readonly) ASRectTable, id> *elementToRectTable; + @end @implementation ASLayout @@ -69,6 +85,7 @@ - (instancetype)initWithLayoutElement:(id)layoutElement #endif _layoutElement = layoutElement; + // Read this now to avoid @c weak overhead later. _layoutElementType = layoutElement.layoutElementType; @@ -87,8 +104,16 @@ - (instancetype)initWithLayoutElement:(id)layoutElement } _sublayouts = sublayouts != nil ? [sublayouts copy] : @[]; + + _elementToRectTable = [ASRectTable rectTableForWeakObjectPointers]; + for (ASLayout *layout in sublayouts) { + [_elementToRectTable setRect:layout.frame forKey:layout.layoutElement]; + } + _flattened = NO; + _retainSublayoutLayoutElements = NO; } + return self; } @@ -137,6 +162,28 @@ + (instancetype)layoutWithLayout:(ASLayout *)layout position:(CGPoint)position sublayouts:layout.sublayouts]; } +#pragma mark - Sublayout Elements Caching + +- (void)setRetainSublayoutLayoutElements:(BOOL)retainSublayoutLayoutElements +{ + if (_retainSublayoutLayoutElements != retainSublayoutLayoutElements) { + _retainSublayoutLayoutElements = retainSublayoutLayoutElements; + + if (retainSublayoutLayoutElements == NO) { + _sublayoutLayoutElements = nil; + } else { + // Add sublayouts layout elements to an internal array to retain it while the layout lives + NSUInteger sublayoutCount = _sublayouts.count; + if (sublayoutCount > 0) { + _sublayoutLayoutElements = [NSMutableArray arrayWithCapacity:sublayoutCount]; + for (ASLayout *sublayout in _sublayouts) { + [_sublayoutLayoutElements addObject:sublayout.layoutElement]; + } + } + } + } +} + #pragma mark - Layout Flattening - (ASLayout *)filteredNodeLayoutTree @@ -170,8 +217,10 @@ - (ASLayout *)filteredNodeLayoutTree } queue.insert(queue.cbegin(), sublayoutContexts.begin(), sublayoutContexts.end()); } - - return [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:flattenedSublayouts]; + + ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size position:CGPointZero sublayouts:flattenedSublayouts]; + layout.retainSublayoutLayoutElements = YES; + return layout; } #pragma mark - Accessors @@ -181,6 +230,11 @@ - (ASLayoutElementType)type return _layoutElementType; } +- (CGRect)frameForElement:(id)layoutElement +{ + return [_elementToRectTable rectForKey:layoutElement]; +} + - (CGRect)frame { CGRect subnodeFrame = CGRectZero; @@ -281,4 +335,3 @@ + (instancetype)layoutWithLayoutableObject:(id)layoutElement // Here could specific verfication happen return layout; } - diff --git a/AsyncDisplayKit/Layout/ASLayoutElement.h b/Source/Layout/ASLayoutElement.h similarity index 93% rename from AsyncDisplayKit/Layout/ASLayoutElement.h rename to Source/Layout/ASLayoutElement.h index cb21447507..162156e4f1 100644 --- a/AsyncDisplayKit/Layout/ASLayoutElement.h +++ b/Source/Layout/ASLayoutElement.h @@ -8,18 +8,21 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import -#import -#import -#import #import -#import #import +#import +#import +#import +#import @class ASLayout; @class ASLayoutSpec; @protocol ASLayoutElementStylability; +@protocol ASTraitEnvironment; + +NS_ASSUME_NONNULL_BEGIN + /** A constant that indicates that the parent's size is not yet determined in a given dimension. */ extern CGFloat const ASLayoutElementParentDimensionUndefined; @@ -32,9 +35,19 @@ typedef NS_ENUM(NSUInteger, ASLayoutElementType) { ASLayoutElementTypeDisplayNode }; -NS_ASSUME_NONNULL_BEGIN +ASDISPLAYNODE_EXTERN_C_BEGIN + +/** + This function will walk the layout element hierarchy. It does run the block on the node provided + directly to the function call. + */ +extern void ASLayoutElementPerformBlockOnEveryElement(id root, void(^block)(id element)); + +ASDISPLAYNODE_EXTERN_C_END -/** +#pragma mark - ASLayoutElement + +/** * The ASLayoutElement protocol declares a method for measuring the layout of an object. A layout * is defined by an ASLayout return value, and must specify 1) the size (but not position) of the * layoutElement object, and 2) the size and position of all of its immediate child objects. The tree @@ -50,7 +63,7 @@ NS_ASSUME_NONNULL_BEGIN * access to the options via convenience properties. If you are creating custom layout spec, then you can * extend the backing layout options class to accommodate any new layout options. */ -@protocol ASLayoutElement +@protocol ASLayoutElement #pragma mark - Getter @@ -59,20 +72,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign, readonly) ASLayoutElementType layoutElementType; -/** - * @abstract Returns if the layoutElement can be used to layout in an asynchronous way on a background thread. - */ -@property (nonatomic, assign, readonly) BOOL canLayoutAsynchronous; - /** * @abstract A size constraint that should apply to this ASLayoutElement. */ @property (nonatomic, assign, readonly) ASLayoutElementStyle *style; /** - * @abstract Optional name that is printed by ascii art string and displayed in description. + * @abstract Returns all children of an object which class conforms to the ASLayoutElement protocol */ -@property (nullable, nonatomic, copy) NSString *debugName; +- (nullable NSArray> *)sublayoutElements; #pragma mark - Calculate layout @@ -142,6 +150,8 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Deprecated +#define ASLayoutable ASLayoutElement + /** * @abstract Calculate a layout based on given size range. * @@ -156,6 +166,8 @@ NS_ASSUME_NONNULL_BEGIN @end +#pragma mark - ASLayoutElementStyle + extern NSString * const ASLayoutElementStyleWidthProperty; extern NSString * const ASLayoutElementStyleMinWidthProperty; extern NSString * const ASLayoutElementStyleMaxWidthProperty; @@ -175,13 +187,11 @@ extern NSString * const ASLayoutElementStyleDescenderProperty; extern NSString * const ASLayoutElementStyleLayoutPositionProperty; -#pragma mark - ASLayoutElementStyle - @protocol ASLayoutElementStyleDelegate - (void)style:(__kindof ASLayoutElementStyle *)style propertyDidChange:(NSString *)propertyName; @end -@interface ASLayoutElementStyle : NSObject +@interface ASLayoutElementStyle : NSObject /** * @abstract Initializes the layoutElement style with a specified delegate @@ -244,7 +254,6 @@ extern NSString * const ASLayoutElementStyleLayoutPositionProperty; */ @property (nonatomic, assign, readwrite) ASDimension maxWidth; - #pragma mark - ASLayoutElementStyleSizeHelpers /** @@ -310,12 +319,11 @@ extern NSString * const ASLayoutElementStyleLayoutPositionProperty; @end - #pragma mark - ASLayoutElementStylability @protocol ASLayoutElementStylability -- (instancetype)styledWithBlock:(__attribute__((noescape)) void (^)(__kindof ASLayoutElementStyle *style))styleBlock; +- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock; @end diff --git a/AsyncDisplayKit/Layout/ASLayoutElement.mm b/Source/Layout/ASLayoutElement.mm similarity index 73% rename from AsyncDisplayKit/Layout/ASLayoutElement.mm rename to Source/Layout/ASLayoutElement.mm index 8a89a0212a..d127b6884a 100644 --- a/AsyncDisplayKit/Layout/ASLayoutElement.mm +++ b/Source/Layout/ASLayoutElement.mm @@ -10,22 +10,43 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" +#import +#import +#import +#import +#import #import #import +#if YOGA + #import YOGA_HEADER_PATH +#endif + +extern void ASLayoutElementPerformBlockOnEveryElement(id element, void(^block)(id element)) +{ + if (element) { + block(element); + } + + for (id subelement in element.sublayoutElements) { + ASLayoutElementPerformBlockOnEveryElement(subelement, block); + } +} + +#pragma mark - ASLayoutElementContext + CGFloat const ASLayoutElementParentDimensionUndefined = NAN; CGSize const ASLayoutElementParentSizeUndefined = {ASLayoutElementParentDimensionUndefined, ASLayoutElementParentDimensionUndefined}; int32_t const ASLayoutElementContextInvalidTransitionID = 0; int32_t const ASLayoutElementContextDefaultTransitionID = ASLayoutElementContextInvalidTransitionID + 1; -static inline ASLayoutElementContext _ASLayoutElementContextMake(int32_t transitionID, BOOL needsVisualizeNode) +static inline ASLayoutElementContext _ASLayoutElementContextMake(int32_t transitionID) { struct ASLayoutElementContext context; context.transitionID = transitionID; - context.needsVisualizeNode = needsVisualizeNode; return context; } @@ -34,17 +55,17 @@ static inline BOOL _IsValidTransitionID(int32_t transitionID) return transitionID > ASLayoutElementContextInvalidTransitionID; } -struct ASLayoutElementContext const ASLayoutElementContextNull = _ASLayoutElementContextMake(ASLayoutElementContextInvalidTransitionID, NO); +struct ASLayoutElementContext const ASLayoutElementContextNull = _ASLayoutElementContextMake(ASLayoutElementContextInvalidTransitionID); BOOL ASLayoutElementContextIsNull(struct ASLayoutElementContext context) { return !_IsValidTransitionID(context.transitionID); } -ASLayoutElementContext ASLayoutElementContextMake(int32_t transitionID, BOOL needsVisualizeNode) +ASLayoutElementContext ASLayoutElementContextMake(int32_t transitionID) { NSCAssert(_IsValidTransitionID(transitionID), @"Invalid transition ID"); - return _ASLayoutElementContextMake(transitionID, needsVisualizeNode); + return _ASLayoutElementContextMake(transitionID); } // Note: This is a non-recursive static lock. If it needs to be recursive, use ASDISPLAYNODE_MUTEX_RECURSIVE_INITIALIZER @@ -111,6 +132,7 @@ void ASLayoutElementClearCurrentContext() @implementation ASLayoutElementStyle { ASDN::RecursiveMutex __instanceLock__; ASLayoutElementSize _size; + ASLayoutElementStyleExtensions _extensions; std::atomic _spacingBefore; std::atomic _spacingAfter; @@ -121,6 +143,20 @@ @implementation ASLayoutElementStyle { std::atomic _ascender; std::atomic _descender; std::atomic _layoutPosition; + +#if YOGA + std::atomic _direction; + std::atomic _spacing; + std::atomic _justifyContent; + std::atomic _alignItems; + std::atomic _positionType; + std::atomic _position; + std::atomic _margin; + std::atomic _padding; + std::atomic _border; + std::atomic _aspectRatio; + std::atomic _flexWrap; +#endif } @dynamic width, height, minWidth, maxWidth, minHeight, maxHeight; @@ -333,7 +369,6 @@ - (void)setMaxLayoutSize:(ASLayoutSize)maxLayoutSize ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); } - #pragma mark - ASStackLayoutElement - (void)setSpacingBefore:(CGFloat)spacingBefore @@ -437,6 +472,56 @@ - (CGPoint)layoutPosition return _layoutPosition.load(); } +#pragma mark - Extensions + +- (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx +{ + NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Setting index outside of max bool extensions space"); + + ASDN::MutexLocker l(__instanceLock__); + _extensions.boolExtensions[idx] = value; +} + +- (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx\ +{ + NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Accessing index outside of max bool extensions space"); + + ASDN::MutexLocker l(__instanceLock__); + return _extensions.boolExtensions[idx]; +} + +- (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx +{ + NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Setting index outside of max integer extensions space"); + + ASDN::MutexLocker l(__instanceLock__); + _extensions.integerExtensions[idx] = value; +} + +- (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx +{ + NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Accessing index outside of max integer extensions space"); + + ASDN::MutexLocker l(__instanceLock__); + return _extensions.integerExtensions[idx]; +} + +- (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx +{ + NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Setting index outside of max edge insets extensions space"); + + ASDN::MutexLocker l(__instanceLock__); + _extensions.edgeInsetsExtensions[idx] = value; +} + +- (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx +{ + NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Accessing index outside of max edge insets extensions space"); + + ASDN::MutexLocker l(__instanceLock__); + return _extensions.edgeInsetsExtensions[idx]; +} + #pragma mark - Debugging - (NSString *)description @@ -463,9 +548,7 @@ - (NSString *)description [result addObject:@{ @"maxLayoutSize" : NSStringFromASLayoutSize(self.maxLayoutSize) }]; } - const ASEnvironmentLayoutOptionsState defaultState = ASEnvironmentLayoutOptionsStateMakeDefault(); - - if (self.alignSelf != defaultState.alignSelf) { + if (self.alignSelf != ASStackLayoutAlignSelfAuto) { [result addObject:@{ @"alignSelf" : [@[@"ASStackLayoutAlignSelfAuto", @"ASStackLayoutAlignSelfStart", @"ASStackLayoutAlignSelfEnd", @@ -473,41 +556,71 @@ - (NSString *)description @"ASStackLayoutAlignSelfStretch"] objectAtIndex:self.alignSelf] }]; } - if (self.ascender != defaultState.ascender) { + if (self.ascender != 0) { [result addObject:@{ @"ascender" : @(self.ascender) }]; } - if (self.descender != defaultState.descender) { + if (self.descender != 0) { [result addObject:@{ @"descender" : @(self.descender) }]; } - if (ASDimensionEqualToDimension(self.flexBasis, defaultState.flexBasis) == NO) { + if (ASDimensionEqualToDimension(self.flexBasis, ASDimensionAuto) == NO) { [result addObject:@{ @"flexBasis" : NSStringFromASDimension(self.flexBasis) }]; } - if (self.flexGrow != defaultState.flexGrow) { + if (self.flexGrow != 0) { [result addObject:@{ @"flexGrow" : @(self.flexGrow) }]; } - if (self.flexShrink != defaultState.flexShrink) { + if (self.flexShrink != 0) { [result addObject:@{ @"flexShrink" : @(self.flexShrink) }]; } - if (self.spacingAfter != defaultState.spacingAfter) { + if (self.spacingAfter != 0) { [result addObject:@{ @"spacingAfter" : @(self.spacingAfter) }]; } - if (self.spacingBefore != defaultState.spacingBefore) { + if (self.spacingBefore != 0) { [result addObject:@{ @"spacingBefore" : @(self.spacingBefore) }]; } - if (CGPointEqualToPoint(self.layoutPosition, defaultState.layoutPosition) == NO) { + if (CGPointEqualToPoint(self.layoutPosition, CGPointZero) == NO) { [result addObject:@{ @"layoutPosition" : [NSValue valueWithCGPoint:self.layoutPosition] }]; } return result; } +#pragma mark - Yoga Flexbox Properties + +#if YOGA + +- (ASStackLayoutDirection)direction { return _direction.load(); } +- (CGFloat)spacing { return _spacing.load(); } +- (ASStackLayoutJustifyContent)justifyContent { return _justifyContent.load(); } +- (ASStackLayoutAlignItems)alignItems { return _alignItems.load(); } +- (YGPositionType)positionType { return _positionType.load(); } +- (ASEdgeInsets)position { return _position.load(); } +- (ASEdgeInsets)margin { return _margin.load(); } +- (ASEdgeInsets)padding { return _padding.load(); } +- (ASEdgeInsets)border { return _border.load(); } +- (CGFloat)aspectRatio { return _aspectRatio.load(); } +- (YGWrap)flexWrap { return _flexWrap.load(); } + +- (void)setDirection:(ASStackLayoutDirection)direction { _direction.store(direction); } +- (void)setSpacing:(CGFloat)spacing { _spacing.store(spacing); } +- (void)setJustifyContent:(ASStackLayoutJustifyContent)justify { _justifyContent.store(justify); } +- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems { _alignItems.store(alignItems); } +- (void)setPositionType:(YGPositionType)positionType { _positionType.store(positionType); } +- (void)setPosition:(ASEdgeInsets)position { _position.store(position); } +- (void)setMargin:(ASEdgeInsets)margin { _margin.store(margin); } +- (void)setPadding:(ASEdgeInsets)padding { _padding.store(padding); } +- (void)setBorder:(ASEdgeInsets)border { _border.store(border); } +- (void)setAspectRatio:(CGFloat)aspectRatio { _aspectRatio.store(aspectRatio); } +- (void)setFlexWrap:(YGWrap)flexWrap { _flexWrap.store(flexWrap); } + +#endif + #pragma mark Deprecated #pragma clang diagnostic push @@ -527,4 +640,3 @@ - (void)setSizeRange:(ASRelativeSizeRange)sizeRange #pragma clang diagnostic pop @end - diff --git a/Source/Layout/ASLayoutElementExtensibility.h b/Source/Layout/ASLayoutElementExtensibility.h new file mode 100644 index 0000000000..4bd6b39085 --- /dev/null +++ b/Source/Layout/ASLayoutElementExtensibility.h @@ -0,0 +1,109 @@ +// +// ASLayoutElementExtensibility.h +// AsyncDisplayKit +// +// Created by Michael Schneider on 3/29/16. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import + +#pragma mark - ASLayoutElementExtensibility + +@protocol ASLayoutElementExtensibility + +// The maximum number of extended values per type are defined in ASEnvironment.h above the ASEnvironmentStateExtensions +// struct definition. If you try to set a value at an index after the maximum it will throw an assertion. + +- (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx; +- (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx; + +- (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx; +- (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx; + +- (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx; +- (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx; + +@end + +#pragma mark - Dynamic Properties + +/** + * Unbox NSNumber based on the type + */ +#define ASDK_UNBOX_NUMBER(NUMBER, PROPERTY_TYPE) \ +const char *objCType = [NUMBER objCType]; \ +if (strcmp(objCType, @encode(BOOL)) == 0) { \ + return (PROPERTY_TYPE)[obj boolValue]; \ +} else if (strcmp(objCType, @encode(int)) == 0) { \ + return (PROPERTY_TYPE)[obj intValue]; \ +} else if (strcmp(objCType, @encode(NSInteger)) == 0) { \ + return (PROPERTY_TYPE)[obj integerValue]; \ +} else if (strcmp(objCType, @encode(NSUInteger)) == 0) { \ + return (PROPERTY_TYPE)[obj unsignedIntegerValue]; \ +} else if (strcmp(objCType, @encode(CGFloat)) == 0) { \ + return (PROPERTY_TYPE)[obj floatValue]; \ +} else { \ + NSAssert(NO, @"Data type not supported"); \ +} \ + +/** + * Define a NSObject property + */ +#define ASDK_STYLE_PROP_OBJ(PROPERTY_TYPE, PROPERTY_NAME, SETTER_NAME) \ +@dynamic PROPERTY_NAME; \ +- (PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + return (PROPERTY_TYPE)objc_getAssociatedObject(self, @selector(PROPERTY_NAME)); \ +} \ +\ +- (void)SETTER_NAME:(PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + objc_setAssociatedObject(self, @selector(PROPERTY_NAME), PROPERTY_NAME, OBJC_ASSOCIATION_RETAIN); \ +} \ + +/** + * Define an primitive property + */ +#define ASDK_STYLE_PROP_PRIM(PROPERTY_TYPE, PROPERTY_NAME, SETTER_NAME, DEFAULT_VALUE) \ +@dynamic PROPERTY_NAME; \ +- (PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + id obj = objc_getAssociatedObject(self, @selector(PROPERTY_NAME)); \ + \ + if (obj != nil) { \ + ASDK_UNBOX_NUMBER(obj, PROPERTY_TYPE); \ + } \ + \ + return DEFAULT_VALUE;\ +} \ +\ +- (void)SETTER_NAME:(PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + objc_setAssociatedObject(self, @selector(PROPERTY_NAME), @(PROPERTY_NAME), OBJC_ASSOCIATION_RETAIN); \ +} \ + +/** + * Define an structure property + */ +#define ASDK_STYLE_PROP_STR(PROPERTY_TYPE, PROPERTY_NAME, SETTER_NAME, DEFAULT_STRUCT) \ +@dynamic PROPERTY_NAME; \ +- (PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + id obj = objc_getAssociatedObject(self, @selector(PROPERTY_NAME)); \ + if (obj == nil) { \ + return DEFAULT_STRUCT; \ + } \ + PROPERTY_TYPE PROPERTY_NAME; [obj getValue:&PROPERTY_NAME]; return PROPERTY_NAME; \ +} \ +\ +- (void)SETTER_NAME:(PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + objc_setAssociatedObject(self, @selector(PROPERTY_NAME), [NSValue value:&PROPERTY_NAME withObjCType:@encode(PROPERTY_TYPE)], OBJC_ASSOCIATION_RETAIN_NONATOMIC);\ +} \ diff --git a/AsyncDisplayKit/Layout/ASLayoutElementPrivate.h b/Source/Layout/ASLayoutElementPrivate.h similarity index 68% rename from AsyncDisplayKit/Layout/ASLayoutElementPrivate.h rename to Source/Layout/ASLayoutElementPrivate.h index 6bcd0c4a87..a82f39a0c2 100644 --- a/AsyncDisplayKit/Layout/ASLayoutElementPrivate.h +++ b/Source/Layout/ASLayoutElementPrivate.h @@ -8,14 +8,16 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDimension.h" +#import +#import @protocol ASLayoutElement; @class ASLayoutElementStyle; +#pragma mark - ASLayoutElementContext + struct ASLayoutElementContext { int32_t transitionID; - BOOL needsVisualizeNode; }; extern int32_t const ASLayoutElementContextInvalidTransitionID; @@ -26,7 +28,7 @@ extern struct ASLayoutElementContext const ASLayoutElementContextNull; extern BOOL ASLayoutElementContextIsNull(struct ASLayoutElementContext context); -extern struct ASLayoutElementContext ASLayoutElementContextMake(int32_t transitionID, BOOL needsVisualizeNode); +extern struct ASLayoutElementContext ASLayoutElementContextMake(int32_t transitionID); extern void ASLayoutElementSetCurrentContext(struct ASLayoutElementContext context); @@ -34,11 +36,39 @@ extern struct ASLayoutElementContext ASLayoutElementGetCurrentContext(); extern void ASLayoutElementClearCurrentContext(); + +#pragma mark - ASLayoutElementLayoutDefaults + +#define ASLayoutElementLayoutCalculationDefaults \ +- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize\ +{\ + _Pragma("clang diagnostic push")\ + _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")\ + /* For now we just call the deprecated measureWithSizeRange: method to not break old API */ \ + return [self measureWithSizeRange:constrainedSize]; \ + _Pragma("clang diagnostic pop")\ +} \ +\ +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize\ +{\ + return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max];\ +}\ +\ +- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize\ + restrictedToSize:(ASLayoutElementSize)size\ + relativeToParentSize:(CGSize)parentSize\ +{\ + const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, ASLayoutElementSizeResolve(self.style.size, parentSize));\ + return [self calculateLayoutThatFits:resolvedRange];\ +}\ + + +#pragma mark - ASLayoutElementFinalLayoutElement /** - * The base protocol for ASLayoutElement. Generally the methods/properties in this class do not need to be + * The base protocol for ASLayoutElementFinalLayoutElement. Generally the methods/properties in this class do not need to be * called by the end user and are only called internally. However, there may be a case where the methods are useful. */ -@protocol ASLayoutElementPrivate +@protocol ASLayoutElementFinalLayoutElement /** * @abstract This method can be used to give the user a chance to wrap an ASLayoutElement in an ASLayoutSpec @@ -61,40 +91,65 @@ extern void ASLayoutElementClearCurrentContext(); @end +// Default implementation for ASLayoutElementPrivate that can be used in classes that comply to ASLayoutElementPrivate + +#define ASLayoutElementFinalLayoutElementDefault \ +\ +@synthesize isFinalLayoutElement = _isFinalLayoutElement;\ +\ +- (id)finalLayoutElement\ +{\ + return self;\ +}\ + + #pragma mark - ASLayoutElementExtensibility -#define ASEnvironmentLayoutExtensibilityForwarding \ +// Provides extension points for elments that comply to ASLayoutElement like ASLayoutSpec to add additional +// properties besides the default one provided in ASLayoutElementStyle + +static const int kMaxLayoutElementBoolExtensions = 1; +static const int kMaxLayoutElementStateIntegerExtensions = 4; +static const int kMaxLayoutElementStateEdgeInsetExtensions = 1; + +typedef struct ASLayoutElementStyleExtensions { + // Values to store extensions + BOOL boolExtensions[kMaxLayoutElementBoolExtensions]; + NSInteger integerExtensions[kMaxLayoutElementStateIntegerExtensions]; + UIEdgeInsets edgeInsetsExtensions[kMaxLayoutElementStateEdgeInsetExtensions]; +} ASLayoutElementStyleExtensions; + +#define ASLayoutElementStyleExtensibilityForwarding \ - (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx\ {\ - _ASEnvironmentLayoutOptionsExtensionSetBoolAtIndex(self, idx, value);\ + [self.style setLayoutOptionExtensionBool:value atIndex:idx];\ }\ \ - (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx\ {\ - return _ASEnvironmentLayoutOptionsExtensionGetBoolAtIndex(self, idx);\ + return [self.style layoutOptionExtensionBoolAtIndex:idx];\ }\ \ - (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx\ {\ - _ASEnvironmentLayoutOptionsExtensionSetIntegerAtIndex(self, idx, value);\ + [self.style setLayoutOptionExtensionInteger:value atIndex:idx];\ }\ \ - (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx\ {\ - return _ASEnvironmentLayoutOptionsExtensionGetIntegerAtIndex(self, idx);\ + return [self.style layoutOptionExtensionIntegerAtIndex:idx];\ }\ \ - (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx\ {\ - _ASEnvironmentLayoutOptionsExtensionSetEdgeInsetsAtIndex(self, idx, value);\ + [self.style setLayoutOptionExtensionEdgeInsets:value atIndex:idx];\ }\ \ - (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx\ {\ - return _ASEnvironmentLayoutOptionsExtensionGetEdgeInsetsAtIndex(self, idx);\ + return [self.style layoutOptionExtensionEdgeInsetsAtIndex:idx];\ }\ - #pragma mark ASLayoutElementStyleForwardingDeclaration (Deprecated) #define ASLayoutElementStyleForwardingDeclaration \ diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.h b/Source/Layout/ASLayoutSpec+Subclasses.h similarity index 94% rename from AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.h rename to Source/Layout/ASLayoutSpec+Subclasses.h index 84d5a9db35..ae4549966c 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.h +++ b/Source/Layout/ASLayoutSpec+Subclasses.h @@ -6,10 +6,14 @@ // Copyright © 2016 Facebook. All rights reserved. // -#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN +@protocol ASLayoutElement; + @interface ASLayoutSpec (Subclassing) /** diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.mm b/Source/Layout/ASLayoutSpec+Subclasses.mm similarity index 77% rename from AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.mm rename to Source/Layout/ASLayoutSpec+Subclasses.mm index ed186d4a0c..cc1dbf8f8f 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.mm +++ b/Source/Layout/ASLayoutSpec+Subclasses.mm @@ -6,9 +6,10 @@ // Copyright © 2016 Facebook. All rights reserved. // -#import "ASLayoutSpec+Subclasses.h" -#import "ASLayoutSpec.h" -#import "ASLayoutSpecPrivate.h" +#import + +#import +#import #pragma mark - ASNullLayoutSpec @@ -53,14 +54,7 @@ @implementation ASLayoutSpec (Subclassing) if (self.isFinalLayoutElement == NO) { id finalLayoutElement = [child finalLayoutElement]; if (finalLayoutElement != child) { - if (ASEnvironmentStatePropagationEnabled()) { - ASEnvironmentStatePropagateUp(finalLayoutElement, child.environmentState.layoutOptionsState); - } else { - // If state propagation is not enabled the layout options state needs to be copied manually - ASEnvironmentState finalLayoutElementEnvironmentState = finalLayoutElement.environmentState; - finalLayoutElementEnvironmentState.layoutOptionsState = child.environmentState.layoutOptionsState; - finalLayoutElement.environmentState = finalLayoutElementEnvironmentState; - } + finalLayoutElement.primitiveTraitCollection = child.primitiveTraitCollection; return finalLayoutElement; } } diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.h b/Source/Layout/ASLayoutSpec.h similarity index 83% rename from AsyncDisplayKit/Layout/ASLayoutSpec.h rename to Source/Layout/ASLayoutSpec.h index 6fd5c6b1d7..4a28709d56 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.h +++ b/Source/Layout/ASLayoutSpec.h @@ -10,13 +10,14 @@ #import #import +#import NS_ASSUME_NONNULL_BEGIN /** * A layout spec is an immutable object that describes a layout, loosely inspired by React. */ -@interface ASLayoutSpec : NSObject +@interface ASLayoutSpec : NSObject /** * Creation of a layout spec should only happen by a user in layoutSpecThatFits:. During that method, a @@ -26,31 +27,22 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL isMutable; /** - * Parent of the layout spec - */ -@property (nullable, nonatomic, weak) id parent; - -/** - * Adds a child to this layout spec using a default identifier. - * - * @param child A child to be added. + * First child within the children's array. * * @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the * responsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec, * only require a single child. * * For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example) - * a subclass should use this method to set the "primary" child. It can then use setChild:forIdentifier: + * a subclass should use this method to set the "primary" child. It can then use setChild:atIndex: * to set any other required children. Ideally a subclass would hide this from the user, and use the - * setChild:forIdentifier: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild - * property that behind the scenes is calling setChild:forIdentifier:. + * setChild:atIndex: internally. For example, ASBackgroundLayoutSpec exposes a "background" + * property that behind the scenes is calling setChild:atIndex:. */ @property (nullable, strong, nonatomic) id child; /** - * Adds childen to this layout spec. - * - * @param children An array of ASLayoutElement children to be added. + * An array of ASLayoutElement children * * @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the * reponsibility of holding on to the spec children. Some layout specs, like ASStackLayoutSpec, @@ -60,10 +52,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, strong, nonatomic) NSArray> *children; -@property (nonatomic, assign) BOOL shouldVisualize; -@property (nonatomic, assign) BOOL neverShouldVisualize; -- (void)recursivelySetShouldVisualize:(BOOL)visualize; - @end /** @@ -100,7 +88,7 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASLayoutSpec (Debugging) +@interface ASLayoutSpec (Debugging) /** * Used by other layout specs to create ascii art debug strings */ diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.mm b/Source/Layout/ASLayoutSpec.mm similarity index 76% rename from AsyncDisplayKit/Layout/ASLayoutSpec.mm rename to Source/Layout/ASLayoutSpec.mm index f6e1d94a05..541adbcb8c 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.mm +++ b/Source/Layout/ASLayoutSpec.mm @@ -8,11 +8,15 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASLayoutSpec.h" -#import "ASLayoutSpecPrivate.h" -#import "ASLayoutSpec+Subclasses.h" -#import "ASLayoutElementStylePrivate.h" -#import "ASLayoutSpec+Debug.h" +#import +#import + +#import + +#import +#import +#import +#import #import #import @@ -23,7 +27,6 @@ @implementation ASLayoutSpec // Dynamic properties for ASLayoutElements @dynamic layoutElementType; @synthesize debugName = _debugName; -@synthesize isFinalLayoutElement = _isFinalLayoutElement; #pragma mark - Class @@ -31,7 +34,7 @@ + (void)initialize { [super initialize]; if (self != [ASLayoutSpec class]) { - ASDisplayNodeAssert(!ASSubclassOverridesSelector([ASLayoutSpec class], self, @selector(measureWithSizeRange:)), @"Subclass %@ must not override measureWithSizeRange: method. Instead overwrite calculateLayoutThatFits:", NSStringFromClass(self)); + ASDisplayNodeAssert(!ASSubclassOverridesSelector([ASLayoutSpec class], self, @selector(measureWithSizeRange:)), @"Subclass %@ must not override measureWithSizeRange: method. Instead override calculateLayoutThatFits:", NSStringFromClass(self)); } } @@ -45,7 +48,7 @@ - (instancetype)init } _isMutable = YES; - _environmentState = ASEnvironmentStateMakeDefault(); + _primitiveTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); _childrenArray = [[NSMutableArray alloc] init]; return self; @@ -63,37 +66,7 @@ - (BOOL)canLayoutAsynchronous #pragma mark - Final LayoutElement -- (id)finalLayoutElement -{ - if (ASLayoutElementGetCurrentContext().needsVisualizeNode && !self.neverShouldVisualize) { - return [[ASLayoutSpecVisualizerNode alloc] initWithLayoutSpec:self]; - } else { - return self; - } -} - -- (void)recursivelySetShouldVisualize:(BOOL)visualize -{ - NSMutableArray *mutableChildren = [self.children mutableCopy]; - - for (idlayoutElement in self.children) { - if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) { - ASLayoutSpec *layoutSpec = (ASLayoutSpec *)layoutElement; - - [mutableChildren replaceObjectAtIndex:[mutableChildren indexOfObjectIdenticalTo:layoutSpec] - withObject:[[ASLayoutSpecVisualizerNode alloc] initWithLayoutSpec:layoutSpec]]; - - [layoutSpec recursivelySetShouldVisualize:visualize]; - layoutSpec.shouldVisualize = visualize; - } - } - - if ([mutableChildren count] == 1) { // HACK for wrapper layoutSpecs (e.g. insetLayoutSpec) - self.child = mutableChildren[0]; - } else if ([mutableChildren count] > 1) { - self.children = mutableChildren; - } -} +ASLayoutElementFinalLayoutElementDefault #pragma mark - Style @@ -106,7 +79,7 @@ - (ASLayoutElementStyle *)style return _style; } -- (instancetype)styledWithBlock:(void (^)(ASLayoutElementStyle *style))styleBlock +- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock { styleBlock(self.style); return self; @@ -145,9 +118,6 @@ - (void)setChild:(id)child ASDisplayNodeAssert(_childrenArray.count < 2, @"This layout spec does not support more than one child. Use the setChildren: or the setChild:AtIndex: API"); if (child) { - if (child.layoutElementType == ASLayoutElementTypeLayoutSpec) { - [(ASLayoutSpec *)child setShouldVisualize:self.shouldVisualize]; - } id finalLayoutElement = [self layoutElementToAddFromLayoutElement:child]; if (finalLayoutElement) { _childrenArray[0] = finalLayoutElement; @@ -177,61 +147,51 @@ - (void)setChildren:(NSArray> *)children NSUInteger i = 0; for (id child in children) { ASDisplayNodeAssert([child conformsToProtocol:NSProtocolFromString(@"ASLayoutElement")], @"Child %@ of spec %@ is not an ASLayoutElement!", child, self); - id finalLayoutElement = [self layoutElementToAddFromLayoutElement:child]; - if (finalLayoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) { - [(ASLayoutSpec *)finalLayoutElement setShouldVisualize:self.shouldVisualize]; - } - _childrenArray[i] = finalLayoutElement; + _childrenArray[i] = [self layoutElementToAddFromLayoutElement:child]; i += 1; } } -- (NSArray *)children +- (nullable NSArray> *)children { return [_childrenArray copy]; } -#pragma mark - NSFastEnumeration - -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len +- (NSArray> *)sublayoutElements { - return [_childrenArray countByEnumeratingWithState:state objects:buffer count:len]; + return [_childrenArray copy]; } -#pragma mark - ASEnvironment - -- (ASEnvironmentState)environmentState -{ - return _environmentState; -} +#pragma mark - NSFastEnumeration -- (void)setEnvironmentState:(ASEnvironmentState)environmentState +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len { - _environmentState = environmentState; + return [_childrenArray countByEnumeratingWithState:state objects:buffer count:len]; } -- (BOOL)supportsTraitsCollectionPropagation -{ - return ASEnvironmentStateTraitCollectionPropagationEnabled(); -} +#pragma mark - ASTraitEnvironment -- (ASEnvironmentTraitCollection)environmentTraitCollection +- (ASPrimitiveTraitCollection)primitiveTraitCollection { - return _environmentState.environmentTraitCollection; + return _primitiveTraitCollection; } -- (void)setEnvironmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection +- (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection { - _environmentState.environmentTraitCollection = environmentTraitCollection; + _primitiveTraitCollection = traitCollection; } - (ASTraitCollection *)asyncTraitCollection { ASDN::MutexLocker l(__instanceLock__); - return [ASTraitCollection traitCollectionWithASEnvironmentTraitCollection:self.environmentTraitCollection]; + return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection]; } -ASEnvironmentLayoutExtensibilityForwarding +ASPrimitiveTraitCollectionDeprecatedImplementation + +#pragma mark - ASLayoutElementStyleExtensibility + +ASLayoutElementStyleExtensibilityForwarding #pragma mark - Framework Private diff --git a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h b/Source/Layout/ASOverlayLayoutSpec.h similarity index 84% rename from AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h rename to Source/Layout/ASOverlayLayoutSpec.h index 64ffee4222..8cf4a4647f 100644 --- a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h +++ b/Source/Layout/ASOverlayLayoutSpec.h @@ -20,15 +20,15 @@ NS_ASSUME_NONNULL_BEGIN /** * Overlay layoutElement of this layout spec */ -@property (nullable, nonatomic, strong) id overlay; +@property (nonatomic, strong) id overlay; /** * Creates and returns an ASOverlayLayoutSpec object with a given child and an layoutElement that act as overlay. * * @param child A child that is laid out to determine the size of this spec. - * @param overlay A layoutElement object that is laid out over the child. If this is nil, the overlay is omitted. + * @param overlay A layoutElement object that is laid out over the child. */ -+ (instancetype)overlayLayoutSpecWithChild:(id)child overlay:(nullable id)overlay AS_WARN_UNUSED_RESULT; ++ (instancetype)overlayLayoutSpecWithChild:(id)child overlay:(id)overlay AS_WARN_UNUSED_RESULT; @end diff --git a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm b/Source/Layout/ASOverlayLayoutSpec.mm similarity index 80% rename from AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm rename to Source/Layout/ASOverlayLayoutSpec.mm index 8084b22746..988ddc820c 100644 --- a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm +++ b/Source/Layout/ASOverlayLayoutSpec.mm @@ -8,11 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASOverlayLayoutSpec.h" -#import "ASLayoutSpec+Subclasses.h" - -#import "ASAssert.h" -#import "ASLayoutSpec+Subclasses.h" +#import +#import +#import static NSUInteger const kUnderlayChildIndex = 0; static NSUInteger const kOverlayChildIndex = 1; @@ -33,16 +31,27 @@ - (instancetype)initWithChild:(id)child overlay:(id)child +{ + ASDisplayNodeAssertNotNil(child, @"Child that will be overlayed on shouldn't be nil"); + [super setChild:child atIndex:kUnderlayChildIndex]; +} + +- (id)child +{ + return [super childAtIndex:kUnderlayChildIndex]; +} + - (void)setOverlay:(id)overlay { + ASDisplayNodeAssertNotNil(overlay, @"Overlay cannot be nil"); [super setChild:overlay atIndex:kOverlayChildIndex]; } @@ -60,7 +69,7 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize restrictedToSize:(ASLayoutElementSize)size relativeToParentSize:(CGSize)parentSize { - ASLayout *contentsLayout = [[super childAtIndex:kUnderlayChildIndex] layoutThatFits:constrainedSize parentSize:parentSize]; + ASLayout *contentsLayout = [self.child layoutThatFits:constrainedSize parentSize:parentSize]; contentsLayout.position = CGPointZero; NSMutableArray *sublayouts = [NSMutableArray arrayWithObject:contentsLayout]; if (self.overlay) { diff --git a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.h b/Source/Layout/ASRatioLayoutSpec.h similarity index 95% rename from AsyncDisplayKit/Layout/ASRatioLayoutSpec.h rename to Source/Layout/ASRatioLayoutSpec.h index a80d3d5532..5c59258e79 100644 --- a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.h +++ b/Source/Layout/ASRatioLayoutSpec.h @@ -9,10 +9,11 @@ // #import -#import NS_ASSUME_NONNULL_BEGIN +@protocol ASLayoutElement; + /** Ratio layout spec For when the content should respect a certain inherent ratio but can be scaled (think photos or videos) diff --git a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm b/Source/Layout/ASRatioLayoutSpec.mm similarity index 94% rename from AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm rename to Source/Layout/ASRatioLayoutSpec.mm index 956e0894f0..c0ba6ff329 100644 --- a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm +++ b/Source/Layout/ASRatioLayoutSpec.mm @@ -8,16 +8,16 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASRatioLayoutSpec.h" +#import #import #import #import -#import "ASAssert.h" +#import -#import "ASInternalHelpers.h" -#import "ASLayoutSpec+Subclasses.h" +#import +#import #pragma mark - ASRatioLayoutSpec diff --git a/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h b/Source/Layout/ASRelativeLayoutSpec.h similarity index 91% rename from AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h rename to Source/Layout/ASRelativeLayoutSpec.h index 31e6350849..d91e603457 100644 --- a/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h +++ b/Source/Layout/ASRelativeLayoutSpec.h @@ -12,7 +12,12 @@ #import -/** How the child is positioned within the spec. */ +/** + * How the child is positioned within the spec. + * + * The default option will position the child at point 0. + * Swift: use [] for the default behavior. + */ typedef NS_ENUM(NSUInteger, ASRelativeLayoutSpecPosition) { /** The child is positioned at point 0 */ ASRelativeLayoutSpecPositionNone = 0, @@ -24,7 +29,12 @@ typedef NS_ENUM(NSUInteger, ASRelativeLayoutSpecPosition) { ASRelativeLayoutSpecPositionEnd = 3, }; -/** How much space the spec will take up. */ +/** + * How much space the spec will take up. + * + * The default option will allow the spec to take up the maximum size possible. + * Swift: use [] for the default behavior. + */ typedef NS_OPTIONS(NSUInteger, ASRelativeLayoutSpecSizingOption) { /** The spec will take up the maximum size possible */ ASRelativeLayoutSpecSizingOptionDefault, diff --git a/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm b/Source/Layout/ASRelativeLayoutSpec.mm similarity index 96% rename from AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm rename to Source/Layout/ASRelativeLayoutSpec.mm index 1072fb443d..26cfe2aa11 100644 --- a/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm +++ b/Source/Layout/ASRelativeLayoutSpec.mm @@ -10,10 +10,11 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASRelativeLayoutSpec.h" +#import -#import "ASInternalHelpers.h" -#import "ASLayoutSpec+Subclasses.h" +#import + +#import @implementation ASRelativeLayoutSpec diff --git a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h b/Source/Layout/ASStackLayoutDefines.h similarity index 82% rename from AsyncDisplayKit/Layout/ASStackLayoutDefines.h rename to Source/Layout/ASStackLayoutDefines.h index a45eb8b62a..3b18d71967 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h +++ b/Source/Layout/ASStackLayoutDefines.h @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASBaseDefines.h" +#import /** The direction children are stacked in */ typedef NS_ENUM(NSUInteger, ASStackLayoutDirection) { @@ -66,6 +66,7 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignItems) { ASStackLayoutAlignItemsBaselineFirst, /** Children align to their last baseline. Only available for horizontal stack spec */ ASStackLayoutAlignItemsBaselineLast, + ASStackLayoutAlignItemsNotSet }; /** @@ -85,6 +86,22 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignSelf) { ASStackLayoutAlignSelfStretch, }; +/** Whether children are stacked into a single or multiple lines. */ +typedef NS_ENUM(NSUInteger, ASStackLayoutFlexWrap) { + ASStackLayoutFlexWrapNoWrap, + ASStackLayoutFlexWrapWrap, +}; + +/** Orientation of lines along cross axis if there are multiple lines. */ +typedef NS_ENUM(NSUInteger, ASStackLayoutAlignContent) { + ASStackLayoutAlignContentStart, + ASStackLayoutAlignContentCenter, + ASStackLayoutAlignContentEnd, + ASStackLayoutAlignContentSpaceBetween, + ASStackLayoutAlignContentSpaceAround, + ASStackLayoutAlignContentStretch, +}; + /** Orientation of children along horizontal axis */ typedef NS_ENUM(NSUInteger, ASHorizontalAlignment) { /** No alignment specified. Default value */ @@ -98,11 +115,11 @@ typedef NS_ENUM(NSUInteger, ASHorizontalAlignment) { // After 2.0 has landed, we'll add ASDISPLAYNODE_DEPRECATED here - for now, avoid triggering errors for projects with -Werror /** @deprecated Use ASHorizontalAlignmentLeft instead */ - ASAlignmentLeft = ASHorizontalAlignmentLeft, + ASAlignmentLeft ASDISPLAYNODE_DEPRECATED = ASHorizontalAlignmentLeft, /** @deprecated Use ASHorizontalAlignmentMiddle instead */ - ASAlignmentMiddle = ASHorizontalAlignmentMiddle, + ASAlignmentMiddle ASDISPLAYNODE_DEPRECATED = ASHorizontalAlignmentMiddle, /** @deprecated Use ASHorizontalAlignmentRight instead */ - ASAlignmentRight = ASHorizontalAlignmentRight, + ASAlignmentRight ASDISPLAYNODE_DEPRECATED = ASHorizontalAlignmentRight, }; /** Orientation of children along vertical axis */ @@ -118,9 +135,9 @@ typedef NS_ENUM(NSUInteger, ASVerticalAlignment) { // After 2.0 has landed, we'll add ASDISPLAYNODE_DEPRECATED here - for now, avoid triggering errors for projects with -Werror /** @deprecated Use ASVerticalAlignmentTop instead */ - ASAlignmentTop = ASVerticalAlignmentTop, + ASAlignmentTop ASDISPLAYNODE_DEPRECATED = ASVerticalAlignmentTop, /** @deprecated Use ASVerticalAlignmentCenter instead */ - ASAlignmentCenter = ASVerticalAlignmentCenter, + ASAlignmentCenter ASDISPLAYNODE_DEPRECATED = ASVerticalAlignmentCenter, /** @deprecated Use ASVerticalAlignmentBottom instead */ - ASAlignmentBottom = ASVerticalAlignmentBottom, + ASAlignmentBottom ASDISPLAYNODE_DEPRECATED = ASVerticalAlignmentBottom, }; diff --git a/AsyncDisplayKit/Layout/ASStackLayoutElement.h b/Source/Layout/ASStackLayoutElement.h similarity index 92% rename from AsyncDisplayKit/Layout/ASStackLayoutElement.h rename to Source/Layout/ASStackLayoutElement.h index 9ff912bb07..e635707df1 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutElement.h +++ b/Source/Layout/ASStackLayoutElement.h @@ -8,8 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDimension.h" -#import "ASStackLayoutDefines.h" +#import + +#import NS_ASSUME_NONNULL_BEGIN @@ -46,13 +47,14 @@ NS_ASSUME_NONNULL_BEGIN /** * @abstract Specifies the initial size in the stack dimension for this object. - * Default to ASDimensionAuto + * Defaults to ASDimensionAuto. * Used when attached to a stack layout. */ @property (nonatomic, readwrite) ASDimension flexBasis; /** - * @abstract Orientation of the object along cross axis, overriding alignItems + * @abstract Orientation of the object along cross axis, overriding alignItems. + * Defaults to ASStackLayoutAlignSelfAuto. * Used when attached to a stack layout. */ @property (nonatomic, readwrite) ASStackLayoutAlignSelf alignSelf; diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h b/Source/Layout/ASStackLayoutSpec.h similarity index 64% rename from AsyncDisplayKit/Layout/ASStackLayoutSpec.h rename to Source/Layout/ASStackLayoutSpec.h index 448c540ffe..703099a5c4 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h +++ b/Source/Layout/ASStackLayoutSpec.h @@ -55,12 +55,16 @@ NS_ASSUME_NONNULL_BEGIN Thus, it is preferred to those properties */ @property (nonatomic, assign) ASVerticalAlignment verticalAlignment; -/** The amount of space between each child. */ +/** The amount of space between each child. Defaults to ASStackLayoutJustifyContentStart */ @property (nonatomic, assign) ASStackLayoutJustifyContent justifyContent; -/** Orientation of children along cross axis */ +/** Orientation of children along cross axis. Defaults to ASStackLayoutAlignItemsStretch */ @property (nonatomic, assign) ASStackLayoutAlignItems alignItems; -/** If YES the vertical spacing between two views is measured from the last baseline of the top view to the top of the bottom view */ -@property (nonatomic, assign) BOOL baselineRelativeArrangement; +/** Whether children are stacked into a single or multiple lines. Defaults to single line (ASStackLayoutFlexWrapNoWrap) */ +@property (nonatomic, assign) ASStackLayoutFlexWrap flexWrap; +/** Orientation of lines along cross axis if there are multiple lines. Defaults to ASStackLayoutAlignContentStart */ +@property (nonatomic, assign) ASStackLayoutAlignContent alignContent; +/** Whether this stack can dispatch to other threads, regardless of which thread it's running on */ +@property (nonatomic, assign, getter=isConcurrent) BOOL concurrent; - (instancetype)init; @@ -71,7 +75,28 @@ NS_ASSUME_NONNULL_BEGIN @param alignItems Orientation of the children along the cross axis @param children ASLayoutElement children to be positioned. */ -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray> *)children AS_WARN_UNUSED_RESULT; ++ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction + spacing:(CGFloat)spacing + justifyContent:(ASStackLayoutJustifyContent)justifyContent + alignItems:(ASStackLayoutAlignItems)alignItems + children:(NSArray> *)children AS_WARN_UNUSED_RESULT; + +/** + @param direction The direction of the stack view (horizontal or vertical) + @param spacing The spacing between the children + @param justifyContent If no children are flexible, this describes how to fill any extra space + @param alignItems Orientation of the children along the cross axis + @param flexWrap Whether children are stacked into a single or multiple lines + @param alignContent Orientation of lines along cross axis if there are multiple lines + @param children ASLayoutElement children to be positioned. + */ ++ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction + spacing:(CGFloat)spacing + justifyContent:(ASStackLayoutJustifyContent)justifyContent + alignItems:(ASStackLayoutAlignItems)alignItems + flexWrap:(ASStackLayoutFlexWrap)flexWrap + alignContent:(ASStackLayoutAlignContent)alignContent + children:(NSArray> *)children AS_WARN_UNUSED_RESULT; /** * @return A stack layout spec with direction of ASStackLayoutDirectionVertical diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/Source/Layout/ASStackLayoutSpec.mm similarity index 67% rename from AsyncDisplayKit/Layout/ASStackLayoutSpec.mm rename to Source/Layout/ASStackLayoutSpec.mm index 56a7a6d76c..30eea4950b 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/Source/Layout/ASStackLayoutSpec.mm @@ -8,30 +8,34 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import + #import #import -#import "ASInternalHelpers.h" - -#import "ASLayoutElement.h" -#import "ASLayoutElementStylePrivate.h" -#import "ASLayoutSpecUtilities.h" -#import "ASStackBaselinePositionedLayout.h" -#import "ASThread.h" +#import +#import +#import +#import +#import +#import +#import @implementation ASStackLayoutSpec -{ - ASDN::RecursiveMutex __instanceLock__; -} - (instancetype)init { - return [self initWithDirection:ASStackLayoutDirectionHorizontal spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStretch children:nil]; + return [self initWithDirection:ASStackLayoutDirectionHorizontal spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStretch flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart children:nil]; } + (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children { - return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems children:children]; + return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart children:children]; +} + ++ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray> *)children +{ + return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent children:children]; } + (instancetype)verticalStackLayoutSpec @@ -48,7 +52,7 @@ + (instancetype)horizontalStackLayoutSpec return stackLayoutSpec; } -- (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children +- (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray *)children { if (!(self = [super init])) { return nil; @@ -59,6 +63,8 @@ - (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGF _verticalAlignment = ASVerticalAlignmentNone; _alignItems = alignItems; _justifyContent = justifyContent; + _flexWrap = flexWrap; + _alignContent = alignContent; [self setChildren:children]; return self; @@ -114,12 +120,6 @@ - (void)setSpacing:(CGFloat)spacing _spacing = spacing; } -- (void)setBaselineRelativeArrangement:(BOOL)baselineRelativeArrangement -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _baselineRelativeArrangement = baselineRelativeArrangement; -} - - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { NSArray *children = self.children; @@ -134,56 +134,22 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize return {child, style, style.size}; }); - const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .baselineRelativeArrangement = _baselineRelativeArrangement}; + const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .flexWrap = _flexWrap, .alignContent = _alignContent}; - // First pass is to get the children into a positioned state - const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize); + const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize, _concurrent); const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize); - // Figure out if a baseline pass is really needed - const BOOL directionIsVertical = (style.direction == ASStackLayoutDirectionVertical); - const BOOL needsBaselineAlignment = ASStackBaselinePositionedLayout::needsBaselineAlignment(style); - const BOOL needsBaselinePositioning = (directionIsVertical == NO || needsBaselineAlignment == YES); - - NSMutableArray *sublayouts = [NSMutableArray array]; - CGSize finalSize = CGSizeZero; - if (needsBaselinePositioning) { - // All horizontal stacks, regardless of whether or not they are baseline aligned, should go through a baseline - // computation. They could be used in another horizontal stack that is baseline aligned and will need to have - // computed the proper ascender/descender. - - // Vertical stacks do not need to go through this computation since we can easily compute ascender/descender by - // looking at their first/last child's ascender/descender. - const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize); - - if (directionIsVertical == NO) { - self.style.ascender = baselinePositionedLayout.ascender; - self.style.descender = baselinePositionedLayout.descender; - } - - if (needsBaselineAlignment == YES) { - finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize); - - for (const auto &l : baselinePositionedLayout.items) { - [sublayouts addObject:l.layout]; - } - } - } - - if (directionIsVertical == YES) { + if (style.direction == ASStackLayoutDirectionVertical) { self.style.ascender = stackChildren.front().style.ascender; self.style.descender = stackChildren.back().style.descender; } - if (needsBaselineAlignment == NO) { - finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, positionedLayout.crossSize); - - for (const auto &l : positionedLayout.items) { - [sublayouts addObject:l.layout]; - } + NSMutableArray *sublayouts = [NSMutableArray array]; + for (const auto &item : positionedLayout.items) { + [sublayouts addObject:item.layout]; } - - return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, finalSize) sublayouts:sublayouts]; + + return [ASLayout layoutWithLayoutElement:self size:positionedLayout.size sublayouts:sublayouts]; } - (void)resolveHorizontalAlignment diff --git a/AsyncDisplayKit/Private/ASBasicImageDownloaderInternal.h b/Source/Private/ASBasicImageDownloaderInternal.h similarity index 100% rename from AsyncDisplayKit/Private/ASBasicImageDownloaderInternal.h rename to Source/Private/ASBasicImageDownloaderInternal.h diff --git a/AsyncDisplayKit/Private/ASBatchFetching.h b/Source/Private/ASBatchFetching.h similarity index 77% rename from AsyncDisplayKit/Private/ASBatchFetching.h rename to Source/Private/ASBatchFetching.h index 2f9b8bbed8..eb4c4aa9de 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.h +++ b/Source/Private/ASBatchFetching.h @@ -10,11 +10,12 @@ #import -#import "ASBatchContext.h" -#import "ASScrollDirection.h" +#import ASDISPLAYNODE_EXTERN_C_BEGIN +@class ASBatchContext; + @protocol ASBatchFetchingScrollView - (BOOL)canBatchFetch; @@ -27,31 +28,36 @@ ASDISPLAYNODE_EXTERN_C_BEGIN @abstract Determine if batch fetching should begin based on the state of the parameters. @discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and * ASCollectionView batch fetching API. - @param context The scroll view that in-flight fetches are happening. + @param scrollView The scroll view that in-flight fetches are happening. @param scrollDirection The current scrolling direction of the scroll view. - @param targetOffset The offset that the scrollview will scroll to. + @param scrollableDirections The possible scrolling directions of the scroll view. + @param contentOffset The offset that the scrollview will scroll to. @return Whether or not the current state should proceed with batch fetching. */ -BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset); +BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, ASScrollDirection scrollableDirections, CGPoint contentOffset); /** @abstract Determine if batch fetching should begin based on the state of the parameters. @param context The batch fetching context that contains knowledge about in-flight fetches. @param scrollDirection The current scrolling direction of the scroll view. + @param scrollableDirections The possible scrolling directions of the scroll view. @param bounds The bounds of the scrollview. @param contentSize The content size of the scrollview. @param targetOffset The offset that the scrollview will scroll to. @param leadingScreens How many screens in the remaining distance will trigger batch fetching. + @param visible Whether the view is visible or not. @return Whether or not the current state should proceed with batch fetching. @discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and * ASCollectionView batch fetching API. */ extern BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, ASScrollDirection scrollDirection, + ASScrollDirection scrollableDirections, CGRect bounds, CGSize contentSize, CGPoint targetOffset, - CGFloat leadingScreens); + CGFloat leadingScreens, + BOOL visible); ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Private/ASBatchFetching.m b/Source/Private/ASBatchFetching.m similarity index 61% rename from AsyncDisplayKit/Private/ASBatchFetching.m rename to Source/Private/ASBatchFetching.m index 0dd7cda675..95b02e8e96 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.m +++ b/Source/Private/ASBatchFetching.m @@ -8,9 +8,10 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASBatchFetching.h" +#import +#import -BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset) +BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, ASScrollDirection scrollableDirections, CGPoint contentOffset) { // Don't fetch if the scroll view does not allow if (![scrollView canBatchFetch]) { @@ -22,34 +23,32 @@ BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView NS_ASSUME_NONNULL_BEGIN +@class ASCollectionElement; + @protocol ASCellNodeInteractionDelegate /** @@ -29,6 +31,13 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged; +/** + * Notifies the delegate that a specified cell node invalidates it's size what could result into a size change. + * + * @param node A node informing the delegate about the relayout. + */ +- (void)nodeDidInvalidateSize:(ASCellNode *)node; + /* * Methods to be called whenever the selection or highlight state changes * on ASCellNode. UIKit internally stores these values to update reusable cells. @@ -58,12 +67,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, strong, nullable) UICollectionViewLayoutAttributes *layoutAttributes; -/// readwrite variant of the readonly public property. -@property (nonatomic, copy, nullable) NSString *supplementaryElementKind; +@property (weak, nullable) ASCollectionElement *collectionElement; -@property (nonatomic, copy, nullable) NSIndexPath *cachedIndexPath; +@property (nonatomic, weak, nullable) ASDisplayNode *owningNode; -@property (weak, nonatomic, nullable) ASDisplayNode *owningNode; +@property (nonatomic, assign) BOOL shouldUseUIKitCell; @end diff --git a/Source/Private/ASCollectionLayout.h b/Source/Private/ASCollectionLayout.h new file mode 100644 index 0000000000..76260a0f9f --- /dev/null +++ b/Source/Private/ASCollectionLayout.h @@ -0,0 +1,51 @@ +// +// ASCollectionLayout.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 28/2/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import +#import + +@protocol ASCollectionLayoutDelegate; +@class ASElementMap, ASCollectionLayout, ASCollectionNode; + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED + +@interface ASCollectionLayout : UICollectionViewLayout + +/** + * The collection node object currently using this layout object. + * + * @discussion The collection node object sets the value of this property when a new layout object is assigned to it. + * + * @discussion To get the truth on the current state of the collection, call methods on the collection node or the data source rather than the collection view because: + * 1. The view might not yet be allocated. + * 2. The collection node and data source are thread-safe. + */ +@property (nonatomic, weak) ASCollectionNode *collectionNode; + +@property (nonatomic, strong, readonly) id layoutDelegate; + +/** + * Initializes with a layout delegate. + * + * @discussion For developers' convenience, the delegate is retained by this layout object, similar to UICollectionView retains its UICollectionViewLayout object. + * + * @discussion For simplicity, the delegate is read-only. If a new layout delegate is needed, construct a new layout object with that delegate and notify ASCollectionView about it. + * This ensures the underlying UICollectionView purges its cache and properly loads the new layout. + */ +- (instancetype)initWithLayoutDelegate:(id)layoutDelegate NS_DESIGNATED_INITIALIZER; + +- (instancetype)init __unavailable; + +- (instancetype)initWithCoder:(NSCoder *)aDecoder __unavailable; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASCollectionLayout.mm b/Source/Private/ASCollectionLayout.mm new file mode 100644 index 0000000000..2ce144568e --- /dev/null +++ b/Source/Private/ASCollectionLayout.mm @@ -0,0 +1,153 @@ +// +// ASCollectionLayout.mm +// AsyncDisplayKit +// +// Created by Huy Nguyen on 28/2/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +@interface ASCollectionLayout () { + ASDN::Mutex __instanceLock__; // Non-recursive mutex, ftw! + + // Main thread only. + ASCollectionLayoutState *_state; + + // The pending state calculated ahead of time, if any. + ASCollectionLayoutState *_pendingState; + // The context used to calculate _pendingState + ASCollectionLayoutContext *_layoutContextForPendingState; + + BOOL _layoutDelegateImplementsAdditionalInfoForLayoutWithElements; +} + +@end + +@implementation ASCollectionLayout + +- (instancetype)initWithLayoutDelegate:(id)layoutDelegate +{ + self = [super init]; + if (self) { + ASDisplayNodeAssertNotNil(layoutDelegate, @"Collection layout delegate cannot be nil"); + _layoutDelegate = layoutDelegate; + _layoutDelegateImplementsAdditionalInfoForLayoutWithElements = [layoutDelegate respondsToSelector:@selector(additionalInfoForLayoutWithElements:)]; + } + return self; +} + +#pragma mark - ASDataControllerLayoutDelegate + +- (id)layoutContextWithElements:(ASElementMap *)elements +{ + ASDisplayNodeAssertMainThread(); + id additionalInfo = nil; + if (_layoutDelegateImplementsAdditionalInfoForLayoutWithElements) { + additionalInfo = [_layoutDelegate additionalInfoForLayoutWithElements:elements]; + } + return [[ASCollectionLayoutContext alloc] initWithViewportSize:[self viewportSize] elements:elements additionalInfo:additionalInfo]; +} + +- (void)prepareLayoutWithContext:(id)context +{ + ASCollectionLayoutState *state = [_layoutDelegate calculateLayoutWithContext:context]; + + ASDN::MutexLocker l(__instanceLock__); + _pendingState = state; + _layoutContextForPendingState = context; +} + +#pragma mark - UICollectionViewLayout overrides + +- (void)prepareLayout +{ + ASDisplayNodeAssertMainThread(); + [super prepareLayout]; + ASCollectionLayoutContext *context = [self layoutContextWithElements:_collectionNode.visibleElements]; + + ASCollectionLayoutState *state = nil; + { + ASDN::MutexLocker l(__instanceLock__); + if (_pendingState != nil && ASObjectIsEqual(_layoutContextForPendingState, context)) { + // Looks like we can use the pending state. Great! + state = _pendingState; + _pendingState = nil; + _layoutContextForPendingState = nil; + } + } + + if (state == nil) { + state = [_layoutDelegate calculateLayoutWithContext:context]; + } + + _state = state; +} + +- (void)invalidateLayout +{ + ASDisplayNodeAssertMainThread(); + [super invalidateLayout]; + _state = nil; +} + +- (CGSize)collectionViewContentSize +{ + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertNotNil(_state, @"Collection layout state should not be nil at this point"); + return _state.contentSize; +} + +- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect +{ + NSMutableArray *attributesInRect = [NSMutableArray array]; + NSMapTable *attrsMap = _state.elementToLayoutArrtibutesMap; + for (ASCollectionElement *element in attrsMap) { + UICollectionViewLayoutAttributes *attrs = [attrsMap objectForKey:element]; + if (CGRectIntersectsRect(rect, attrs.frame)) { + [attributesInRect addObject:attrs]; + } + } + return attributesInRect; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath +{ + ASCollectionLayoutState *state = _state; + ASCollectionElement *element = [state.elements elementForItemAtIndexPath:indexPath]; + return [state.elementToLayoutArrtibutesMap objectForKey:element]; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath +{ + ASCollectionLayoutState *state = _state; + ASCollectionElement *element = [state.elements supplementaryElementOfKind:elementKind atIndexPath:indexPath]; + return [state.elementToLayoutArrtibutesMap objectForKey:element]; +} + +#pragma mark - Private methods + +- (CGSize)viewportSize +{ + ASCollectionNode *collectionNode = _collectionNode; + if (collectionNode != nil && !collectionNode.isNodeLoaded) { + // TODO consider calculatedSize as well + return collectionNode.threadSafeBounds.size; + } else { + ASDisplayNodeAssertMainThread(); + return self.collectionView.bounds.size; + } +} + +@end diff --git a/Source/Private/ASCollectionLayoutContext+Private.h b/Source/Private/ASCollectionLayoutContext+Private.h new file mode 100644 index 0000000000..eb58c5e5d9 --- /dev/null +++ b/Source/Private/ASCollectionLayoutContext+Private.h @@ -0,0 +1,19 @@ +// +// ASCollectionLayoutContext+Private.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 10/4/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASCollectionLayoutContext (Private) + +- (instancetype)initWithViewportSize:(CGSize)viewportSize elements:(ASElementMap *)elements additionalInfo:(nullable id)additionalInfo; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Private/ASCollectionView+Undeprecated.h b/Source/Private/ASCollectionView+Undeprecated.h similarity index 97% rename from AsyncDisplayKit/Private/ASCollectionView+Undeprecated.h rename to Source/Private/ASCollectionView+Undeprecated.h index 44683e8359..8a1cfc8477 100644 --- a/AsyncDisplayKit/Private/ASCollectionView+Undeprecated.h +++ b/Source/Private/ASCollectionView+Undeprecated.h @@ -6,7 +6,7 @@ // Copyright © 2016 Facebook. All rights reserved. // -#import +#import NS_ASSUME_NONNULL_BEGIN @@ -131,7 +131,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Perform a batch of updates asynchronously. This method must be called from the main thread. @@ -142,7 +142,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Reload everything from scratch, destroying the working range and all cached nodes. diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/Source/Private/ASCollectionViewFlowLayoutInspector.h similarity index 74% rename from AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h rename to Source/Private/ASCollectionViewFlowLayoutInspector.h index 52df1ee1d4..8505d3d7bd 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/Source/Private/ASCollectionViewFlowLayoutInspector.h @@ -8,21 +8,23 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#pragma once - -#include "ASCollectionViewLayoutInspector.h" +#import NS_ASSUME_NONNULL_BEGIN +@class ASCollectionView; +@class UICollectionViewFlowLayout; + /** * A layout inspector implementation specific for the sizing behavior of UICollectionViewFlowLayouts */ +AS_SUBCLASSING_RESTRICTED @interface ASCollectionViewFlowLayoutInspector : NSObject @property (nonatomic, weak, readonly) UICollectionViewFlowLayout *layout; - (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout NS_DESIGNATED_INITIALIZER; @end diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/Source/Private/ASCollectionViewFlowLayoutInspector.m similarity index 50% rename from AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m rename to Source/Private/ASCollectionViewFlowLayoutInspector.m index 5c49d17ea2..2ee8159da6 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m +++ b/Source/Private/ASCollectionViewFlowLayoutInspector.m @@ -8,11 +8,12 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASCollectionViewFlowLayoutInspector.h" -#import "ASCollectionView.h" -#import "ASAssert.h" -#import "ASEqualityHelpers.h" -#import "ASCollectionView+Undeprecated.h" +#import +#import +#import +#import +#import +#import #define kDefaultItemSize CGSizeMake(50, 50) @@ -24,28 +25,23 @@ @interface ASCollectionViewFlowLayoutInspector () @implementation ASCollectionViewFlowLayoutInspector { struct { + unsigned int implementsSizeRangeForHeader:1; unsigned int implementsReferenceSizeForHeader:1; + unsigned int implementsSizeRangeForFooter:1; unsigned int implementsReferenceSizeForFooter:1; unsigned int implementsConstrainedSizeForNodeAtIndexPathDeprecated:1; unsigned int implementsConstrainedSizeForItemAtIndexPath:1; } _delegateFlags; - - struct { - unsigned int implementsNumberOfSectionsInCollectionView:1; - } _dataSourceFlags; } #pragma mark Lifecycle -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout; +- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout; { - NSParameterAssert(collectionView); NSParameterAssert(flowLayout); self = [super init]; if (self != nil) { - [self didChangeCollectionViewDataSource:collectionView.asyncDataSource]; - [self didChangeCollectionViewDelegate:collectionView.asyncDelegate]; _layout = flowLayout; } return self; @@ -58,105 +54,105 @@ - (void)didChangeCollectionViewDelegate:(id)delegate; if (delegate == nil) { memset(&_delegateFlags, 0, sizeof(_delegateFlags)); } else { + _delegateFlags.implementsSizeRangeForHeader = [delegate respondsToSelector:@selector(collectionNode:sizeRangeForHeaderInSection:)]; _delegateFlags.implementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; + _delegateFlags.implementsSizeRangeForFooter = [delegate respondsToSelector:@selector(collectionNode:sizeRangeForFooterInSection:)]; _delegateFlags.implementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; _delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; _delegateFlags.implementsConstrainedSizeForItemAtIndexPath = [delegate respondsToSelector:@selector(collectionNode:constrainedSizeForItemAtIndexPath:)]; } } -- (void)didChangeCollectionViewDataSource:(id)dataSource -{ - if (dataSource == nil) { - memset(&_dataSourceFlags, 0, sizeof(_dataSourceFlags)); - } else { - _dataSourceFlags.implementsNumberOfSectionsInCollectionView = [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; - } -} - - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { + ASSizeRange result = ASSizeRangeUnconstrained; if (_delegateFlags.implementsConstrainedSizeForItemAtIndexPath) { - return [collectionView.asyncDelegate collectionNode:collectionView.collectionNode constrainedSizeForItemAtIndexPath:indexPath]; + result = [collectionView.asyncDelegate collectionNode:collectionView.collectionNode constrainedSizeForItemAtIndexPath:indexPath]; } else if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; + result = [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; #pragma clang diagnostic pop } else { // With 2.0 `collectionView:constrainedSizeForNodeAtIndexPath:` was moved to the delegate. Assert if not implemented on the delegate but on the data source ASDisplayNodeAssert([collectionView.asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] == NO, @"collectionView:constrainedSizeForNodeAtIndexPath: was moved from the ASCollectionDataSource to the ASCollectionDelegate."); } - - CGSize itemSize = _layout.itemSize; - if (CGSizeEqualToSize(itemSize, kDefaultItemSize) == NO) { - return ASSizeRangeMake(itemSize, itemSize); + + // If we got no size range: + if (ASSizeRangeEqualToSizeRange(result, ASSizeRangeUnconstrained)) { + // Use itemSize if they set it. + CGSize itemSize = _layout.itemSize; + if (CGSizeEqualToSize(itemSize, kDefaultItemSize) == NO) { + result = ASSizeRangeMake(itemSize, itemSize); + } else { + // Compute constraint from scroll direction otherwise. + result = NodeConstrainedSizeForScrollDirection(collectionView); + } } - return NodeConstrainedSizeForScrollDirection(collectionView); + return result; } - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - CGSize constrainedSize; - CGSize supplementarySize = [self sizeForSupplementaryViewOfKind:kind inSection:indexPath.section collectionView:collectionView]; - if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) { - constrainedSize = CGSizeMake(CGRectGetWidth(collectionView.bounds), supplementarySize.height); - } else { - constrainedSize = CGSizeMake(supplementarySize.width, CGRectGetHeight(collectionView.bounds)); - } - return ASSizeRangeMake(CGSizeZero, constrainedSize); -} - -- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section -{ - return [self layoutHasSupplementaryViewOfKind:kind inSection:section collectionView:collectionView] ? 1 : 0; -} - -- (ASScrollDirection)scrollableDirections -{ - return (self.layout.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASScrollDirectionHorizontalDirections : ASScrollDirectionVerticalDirections; -} - -#pragma mark - Private helpers - -- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView -{ + ASSizeRange result = ASSizeRangeZero; if (ASObjectIsEqual(kind, UICollectionElementKindSectionHeader)) { - if (_delegateFlags.implementsReferenceSizeForHeader) { - return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:section]; + if (_delegateFlags.implementsSizeRangeForHeader) { + result = [[self delegateForCollectionView:collectionView] collectionNode:collectionView.collectionNode sizeRangeForHeaderInSection:indexPath.section]; + } else if (_delegateFlags.implementsReferenceSizeForHeader) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + CGSize exactSize = [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:indexPath.section]; +#pragma clang diagnostic pop + result = ASSizeRangeMake(exactSize); } else { - return [self.layout headerReferenceSize]; + result = ASSizeRangeMake(_layout.headerReferenceSize); } } else if (ASObjectIsEqual(kind, UICollectionElementKindSectionFooter)) { - if (_delegateFlags.implementsReferenceSizeForFooter) { - return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:section]; + if (_delegateFlags.implementsSizeRangeForFooter) { + result = [[self delegateForCollectionView:collectionView] collectionNode:collectionView.collectionNode sizeRangeForFooterInSection:indexPath.section]; + } else if (_delegateFlags.implementsReferenceSizeForFooter) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + CGSize exactSize = [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:indexPath.section]; +#pragma clang diagnostic pop + result = ASSizeRangeMake(exactSize); } else { - return [self.layout footerReferenceSize]; + result = ASSizeRangeMake(_layout.footerReferenceSize); } } else { - return CGSizeZero; + ASDisplayNodeFailAssert(@"Unexpected supplementary kind: %@", kind); + return ASSizeRangeZero; } -} -- (BOOL)layoutHasSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView -{ - CGSize size = [self sizeForSupplementaryViewOfKind:kind inSection:section collectionView:collectionView]; - return [self usedLayoutValueForSize:size] > 0; + if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) { + result.min.width = result.max.width = CGRectGetWidth(collectionView.bounds); + } else { + result.min.height = result.max.height = CGRectGetHeight(collectionView.bounds); + } + return result; } -- (CGFloat)usedLayoutValueForSize:(CGSize)size +- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section { + ASSizeRange constraint = [self collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) { - return size.height; + return (constraint.max.height > 0 ? 1 : 0); } else { - return size.width; + return (constraint.max.width > 0 ? 1 : 0); } } -- (id)delegateForCollectionView:(ASCollectionView *)collectionView +- (ASScrollDirection)scrollableDirections +{ + return (self.layout.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASScrollDirectionHorizontalDirections : ASScrollDirectionVerticalDirections; +} + +#pragma mark - Private helpers + +- (id)delegateForCollectionView:(ASCollectionView *)collectionView { - return (id)collectionView.asyncDelegate; + return (id)collectionView.asyncDelegate; } @end diff --git a/AsyncDisplayKit/Private/ASControlTargetAction.h b/Source/Private/ASControlTargetAction.h similarity index 100% rename from AsyncDisplayKit/Private/ASControlTargetAction.h rename to Source/Private/ASControlTargetAction.h diff --git a/AsyncDisplayKit/Private/ASControlTargetAction.m b/Source/Private/ASControlTargetAction.m similarity index 96% rename from AsyncDisplayKit/Private/ASControlTargetAction.m rename to Source/Private/ASControlTargetAction.m index 03a6ff0641..9769c571de 100644 --- a/AsyncDisplayKit/Private/ASControlTargetAction.m +++ b/Source/Private/ASControlTargetAction.m @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASControlTargetAction.h" +#import @implementation ASControlTargetAction { diff --git a/AsyncDisplayKit/Private/ASDefaultPlayButton.h b/Source/Private/ASDefaultPlayButton.h similarity index 100% rename from AsyncDisplayKit/Private/ASDefaultPlayButton.h rename to Source/Private/ASDefaultPlayButton.h diff --git a/AsyncDisplayKit/Private/ASDefaultPlayButton.m b/Source/Private/ASDefaultPlayButton.m similarity index 96% rename from AsyncDisplayKit/Private/ASDefaultPlayButton.m rename to Source/Private/ASDefaultPlayButton.m index 37012d609e..6078b56e0b 100644 --- a/AsyncDisplayKit/Private/ASDefaultPlayButton.m +++ b/Source/Private/ASDefaultPlayButton.m @@ -10,8 +10,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDefaultPlayButton.h" -#import "_ASDisplayLayer.h" +#import +#import @implementation ASDefaultPlayButton diff --git a/AsyncDisplayKit/Private/ASDefaultPlaybackButton.h b/Source/Private/ASDefaultPlaybackButton.h similarity index 84% rename from AsyncDisplayKit/Private/ASDefaultPlaybackButton.h rename to Source/Private/ASDefaultPlaybackButton.h index 29bacd257c..83d3da5412 100644 --- a/AsyncDisplayKit/Private/ASDefaultPlaybackButton.h +++ b/Source/Private/ASDefaultPlaybackButton.h @@ -10,11 +10,13 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import -typedef enum { +#import + +typedef NS_ENUM(NSInteger, ASDefaultPlaybackButtonType) { ASDefaultPlaybackButtonTypePlay, ASDefaultPlaybackButtonTypePause -} ASDefaultPlaybackButtonType; +}; + @interface ASDefaultPlaybackButton : ASControlNode @property (nonatomic, assign) ASDefaultPlaybackButtonType buttonType; @end diff --git a/AsyncDisplayKit/Private/ASDefaultPlaybackButton.m b/Source/Private/ASDefaultPlaybackButton.m similarity index 92% rename from AsyncDisplayKit/Private/ASDefaultPlaybackButton.m rename to Source/Private/ASDefaultPlaybackButton.m index 04c19ef3f6..9379c83b7b 100644 --- a/AsyncDisplayKit/Private/ASDefaultPlaybackButton.m +++ b/Source/Private/ASDefaultPlaybackButton.m @@ -10,7 +10,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDefaultPlaybackButton.h" +#import +#import + @interface ASDefaultPlaybackButton() { ASDefaultPlaybackButtonType _buttonType; @@ -49,7 +51,7 @@ - (void)setButtonType:(ASDefaultPlaybackButtonType)buttonType + (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing { - ASDefaultPlaybackButtonType buttonType = [parameters[@"buttonType"] intValue]; + ASDefaultPlaybackButtonType buttonType = (ASDefaultPlaybackButtonType)[parameters[@"buttonType"] intValue]; UIColor *color = parameters[@"color"]; CGContextRef context = UIGraphicsGetCurrentContext(); diff --git a/AsyncDisplayKit/Private/ASDispatch.h b/Source/Private/ASDispatch.h similarity index 100% rename from AsyncDisplayKit/Private/ASDispatch.h rename to Source/Private/ASDispatch.h diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm similarity index 89% rename from AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm rename to Source/Private/ASDisplayNode+AsyncDisplay.mm index 484b6b76a9..a27cc3897a 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -8,13 +8,13 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "_ASCoreAnimationExtras.h" -#import "_ASAsyncTransaction.h" -#import "_ASDisplayLayer.h" -#import "ASAssert.h" -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASInternalHelpers.h" +#import +#import +#import +#import +#import +#import +#import @interface ASDisplayNode () <_ASDisplayLayerDelegate> @end @@ -34,11 +34,15 @@ @implementation ASDisplayNode (AsyncDisplay) - (NSObject *)drawParameters { - if (_flags.implementsDrawParameters) { + __instanceLock__.lock(); + BOOL implementsDrawParameters = _flags.implementsDrawParameters; + __instanceLock__.unlock(); + + if (implementsDrawParameters) { return [self drawParametersForAsyncLayer:self.asyncLayer]; + } else { + return nil; } - - return nil; } - (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock displayBlocks:(NSMutableArray *)displayBlocks @@ -47,8 +51,10 @@ - (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode if (self.isHidden || self.alpha <= 0.0) { return; } - + + __instanceLock__.lock(); BOOL rasterizingFromAscendent = (_hierarchyState & ASHierarchyStateRasterized); + __instanceLock__.unlock(); // if super node is rasterizing descendants, subnodes will not have had layout calls because they don't have layers if (rasterizingFromAscendent) { @@ -170,11 +176,11 @@ - (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchro BOOL opaque = self.opaque; CGRect bounds = self.bounds; CGFloat contentsScaleForDisplay = _contentsScaleForDisplay; + + __instanceLock__.unlock(); // Capture drawParameters from delegate on main thread, if this node is displaying itself rather than recursively rasterizing. id drawParameters = (shouldBeginRasterizing == NO ? [self drawParameters] : nil); - - __instanceLock__.unlock(); // Only the -display methods should be called if we can't size the graphics buffer to use. if (CGRectIsEmpty(bounds) && (shouldBeginRasterizing || shouldCreateGraphicsContext)) { @@ -224,11 +230,21 @@ - (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchro CGContextRef currentContext = UIGraphicsGetCurrentContext(); UIImage *image = nil; + + ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = nil; + ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = nil; + if (currentContext) { + __instanceLock__.lock(); + willDisplayNodeContentWithRenderingContext = _willDisplayNodeContentWithRenderingContext; + didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext; + __instanceLock__.unlock(); + } + // For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or // _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs. - if (currentContext && _willDisplayNodeContentWithRenderingContext) { - _willDisplayNodeContentWithRenderingContext(currentContext); + if (willDisplayNodeContentWithRenderingContext != nil) { + willDisplayNodeContentWithRenderingContext(currentContext); } // Decide if we use a class or instance method to draw or display. @@ -242,8 +258,8 @@ - (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchro isCancelled:isCancelledBlock isRasterizing:rasterizing]; } - if (currentContext && _didDisplayNodeContentWithRenderingContext) { - _didDisplayNodeContentWithRenderingContext(currentContext); + if (didDisplayNodeContentWithRenderingContext != nil) { + didDisplayNodeContentWithRenderingContext(currentContext); } if (shouldCreateGraphicsContext) { @@ -263,12 +279,17 @@ - (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchro - (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously { ASDisplayNodeAssertMainThread(); - - ASDN::MutexLocker l(__instanceLock__); - + + __instanceLock__.lock(); + if (_hierarchyState & ASHierarchyStateRasterized) { + __instanceLock__.unlock(); return; } + + CALayer *layer = _layer; + + __instanceLock__.unlock(); // for async display, capture the current displaySentinel value to bail early when the job is executed if another is // enqueued @@ -306,10 +327,10 @@ - (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asy UIImage *image = (UIImage *)value; BOOL stretchable = (NO == UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero)); if (stretchable) { - ASDisplayNodeSetupLayerContentsWithResizableImage(_layer, image); + ASDisplayNodeSetupLayerContentsWithResizableImage(layer, image); } else { - _layer.contentsScale = self.contentsScale; - _layer.contents = (id)image.CGImage; + layer.contentsScale = self.contentsScale; + layer.contents = (id)image.CGImage; } [self didDisplayAsyncLayer:self.asyncLayer]; } @@ -323,7 +344,7 @@ - (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asy // while synchronizing the final application of the results to the layer's contents property (completionBlock). // First, look to see if we are expected to join a parent's transaction container. - CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ? : _layer; + CALayer *containerLayer = layer.asyncdisplaykit_parentTransactionContainer ? : layer; // In the case that a transaction does not yet exist (such as for an individual node outside of a container), // this call will allocate the transaction and add it to _ASAsyncTransactionGroup. diff --git a/AsyncDisplayKit/Private/ASDisplayNode+DebugTiming.h b/Source/Private/ASDisplayNode+DebugTiming.h similarity index 100% rename from AsyncDisplayKit/Private/ASDisplayNode+DebugTiming.h rename to Source/Private/ASDisplayNode+DebugTiming.h diff --git a/AsyncDisplayKit/Private/ASDisplayNode+DebugTiming.mm b/Source/Private/ASDisplayNode+DebugTiming.mm similarity index 94% rename from AsyncDisplayKit/Private/ASDisplayNode+DebugTiming.mm rename to Source/Private/ASDisplayNode+DebugTiming.mm index 9e71de8574..66240d5970 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+DebugTiming.mm +++ b/Source/Private/ASDisplayNode+DebugTiming.mm @@ -8,8 +8,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDisplayNode+DebugTiming.h" -#import "ASDisplayNodeInternal.h" +#import +#import @implementation ASDisplayNode (DebugTiming) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/Source/Private/ASDisplayNode+FrameworkPrivate.h similarity index 77% rename from AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h rename to Source/Private/ASDisplayNode+FrameworkPrivate.h index bbad40d6f0..e533b200a3 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/Source/Private/ASDisplayNode+FrameworkPrivate.h @@ -13,11 +13,14 @@ // These methods must never be called or overridden by other classes. // -#import "ASDisplayNode.h" -#import "ASThread.h" +#import +#import +#import NS_ASSUME_NONNULL_BEGIN +@protocol ASInterfaceStateDelegate; + /** Hierarchy state is propagated from nodes to all of their children when certain behaviors are required from the subtree. Examples include rasterization and external driving of the .interfaceState property. @@ -46,7 +49,8 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState) /** One of the supernodes of this node is performing a transition. Any layout calculated during this state should not be applied immediately, but pending until later. */ ASHierarchyStateLayoutPending = 1 << 3, - ASHierarchyStateVisualizeLayout = 1 << 4 + ASHierarchyStateYogaLayoutEnabled = 1 << 4, + ASHierarchyStateYogaLayoutMeasuring = 1 << 5 }; ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState hierarchyState) @@ -56,12 +60,17 @@ ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState) { - return ((hierarchyState & ASHierarchyStateRangeManaged) == ASHierarchyStateRangeManaged); + return ((hierarchyState & ASHierarchyStateRangeManaged) == ASHierarchyStateRangeManaged); +} + +ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesYogaLayoutMeasuring(ASHierarchyState hierarchyState) +{ + return ((hierarchyState & ASHierarchyStateYogaLayoutMeasuring) == ASHierarchyStateYogaLayoutMeasuring); } -ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesVisualizeLayout(ASHierarchyState hierarchyState) +ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesYogaLayoutEnabled(ASHierarchyState hierarchyState) { - return ((hierarchyState & ASHierarchyStateVisualizeLayout) == ASHierarchyStateVisualizeLayout); + return ((hierarchyState & ASHierarchyStateYogaLayoutEnabled) == ASHierarchyStateYogaLayoutEnabled); } ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesRasterized(ASHierarchyState hierarchyState) @@ -95,7 +104,7 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat return [NSString stringWithFormat:@"{ %@ }", [states componentsJoinedByString:@" | "]]; } -@interface ASDisplayNode () +@interface ASDisplayNode () { @protected ASInterfaceState _interfaceState; @@ -105,6 +114,12 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat // The view class to use when creating a new display node instance. Defaults to _ASDisplayView. + (Class)viewClass; +// Thread safe way to access the bounds of the node +@property (nonatomic, assign) CGRect threadSafeBounds; + +// delegate to inform of ASInterfaceState changes (used by ASNodeController) +@property (nonatomic, weak) id interfaceStateDelegate; + // These methods are recursive, and either union or remove the provided interfaceState to all sub-elements. - (void)enterInterfaceState:(ASInterfaceState)interfaceState; - (void)exitInterfaceState:(ASInterfaceState)interfaceState; @@ -115,7 +130,7 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat - (void)exitHierarchyState:(ASHierarchyState)hierarchyState; // Changed before calling willEnterHierarchy / didExitHierarchy. -@property (nonatomic, readwrite, assign, getter = isInHierarchy) BOOL inHierarchy; +@property (readonly, assign, getter = isInHierarchy) BOOL inHierarchy; // Call willEnterHierarchy if necessary and set inHierarchy = YES if visibility notifications are enabled on all of its parents - (void)__enterHierarchy; // Call didExitHierarchy if necessary and set inHierarchy = NO if visibility notifications are enabled on all of its parents @@ -156,6 +171,31 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat */ - (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously; +/** + * @abstract Calls -didExitPreloadState on the receiver and its subnode hierarchy. + * + * @discussion Clears any memory-intensive preloaded content. + * This method is used to notify the node that it should purge any content that is both expensive to fetch and to + * retain in memory. + * + * @see [ASDisplayNode(Subclassing) didExitPreloadState] and [ASDisplayNode(Subclassing) didEnterPreloadState] + */ +- (void)recursivelyClearPreloadedData; + +/** + * @abstract Calls -didEnterPreloadState on the receiver and its subnode hierarchy. + * + * @discussion Fetches content from remote sources for the current node and all subnodes. + * + * @see [ASDisplayNode(Subclassing) didEnterPreloadState] and [ASDisplayNode(Subclassing) didExitPreloadState] + */ +- (void)recursivelyPreload; + +/** + * @abstract Triggers a recursive call to -didEnterPreloadState when the node has an interfaceState of ASInterfaceStatePreload + */ +- (void)setNeedsPreload; + /** * @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO. * @@ -175,18 +215,16 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat - (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState; /** - * @abstract Informs the root node that the intrinsic size of the receiver is no longer valid. - * - * @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know - * that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen. + * @abstract Subclass hook for nodes that are acting as root nodes. This method is called if one of the subnodes + * size is invalidated and may need to result in a different size as the current calculated size. */ -- (void)setNeedsLayoutFromAbove; +- (void)_rootNodeDidInvalidateSize; /** - * @abstract Subclass hook for nodes that are acting as root nodes. This method is called if one of the subnodes - * size is invalidated and may need to result in a different size as the current calculated size. + * @abstract Subclass hook for nodes that are acting as root nodes. This method is called after measurement + * finished in a layout transition but before the measurement completion handler is called */ -- (void)_locked_displayNodeDidInvalidateSizeNewSize:(CGSize)newSize; +- (void)_layoutTransitionMeasurementDidFinish; @end diff --git a/Source/Private/ASDisplayNode+FrameworkSubclasses.h b/Source/Private/ASDisplayNode+FrameworkSubclasses.h new file mode 100644 index 0000000000..5e5f48eaf4 --- /dev/null +++ b/Source/Private/ASDisplayNode+FrameworkSubclasses.h @@ -0,0 +1,33 @@ +// +// ASDisplayNode+FrameworkPrivate.h +// AsyncDisplayKit +// +// 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. +// + +// +// The following methods are ONLY for use by _ASDisplayLayer, _ASDisplayView, and ASDisplayNode. +// These methods must never be called or overridden by other classes. +// + +#import +#import + +// These are included because most internal subclasses need it. +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASDisplayNode () +{ + // Protects access to _view, _layer, _pendingViewState, _subnodes, _supernode, and other properties which are accessed from multiple threads. + @package + ASDN::RecursiveMutex __instanceLock__; +} +@end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm similarity index 89% rename from AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm rename to Source/Private/ASDisplayNode+UIViewBridge.mm index f4e67b7f9a..a5139e0b7f 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -8,13 +8,14 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "_ASCoreAnimationExtras.h" -#import "_ASPendingState.h" -#import "ASInternalHelpers.h" -#import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASPendingStateController.h" +#import +#import +#import +#import +#import +#import +#import +#import /** * The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer. @@ -39,7 +40,6 @@ #if DISPLAYNODE_USE_LOCKS #define _bridge_prologue_read ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssertThreadAffinity(self) #define _bridge_prologue_write ASDN::MutexLocker l(__instanceLock__) -#define _bridge_prologue_write_unlock ASDN::MutexUnlocker u(__instanceLock__) #else #define _bridge_prologue_read ASDisplayNodeAssertThreadAffinity(self) #define _bridge_prologue_write @@ -78,8 +78,6 @@ ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNo #define _setToLayer(layerProperty, layerValueExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNodeGetPendingState(self).layerProperty = (layerValueExpr); } -#define _messageToViewOrLayer(viewAndLayerSelector) (_view ? [_view viewAndLayerSelector] : [_layer viewAndLayerSelector]) - /** * This category implements certain frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node, * with minimal loss in performance. Unlike UIView and CALayer methods, these can be called from a non-main thread until the view or layer is created. @@ -217,10 +215,10 @@ - (CGRect)frame _bridge_prologue_read; // Frame is only defined when transform is identity. -#if DEBUG - // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. - ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode frame] - self.transform must be identity in order to use the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); -#endif +//#if DEBUG +// // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. +// ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode frame] - self.transform must be identity in order to use the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); +//#endif CGPoint position = self.position; CGRect bounds = self.bounds; @@ -254,6 +252,11 @@ - (void)setFrame:(CGRect)rect CGPoint newPosition = CGPointZero; ASBoundsAndPositionForFrame(rect, origin, anchorPoint, &newBounds, &newPosition); + if (ASIsCGRectValidForLayout(newBounds) == NO || ASIsCGPositionValidForLayout(newPosition) == NO) { + ASDisplayNodeAssertNonFatal(NO, @"-[ASDisplayNode setFrame:] - The new frame (%@) is invalid and unsafe to be set.", NSStringFromCGRect(rect)); + return; + } + if (useLayer) { layer.bounds = newBounds; layer.position = newPosition; @@ -276,10 +279,10 @@ - (void)setFrame:(CGRect)rect // We do have to set frame directly, and we're on main thread with a loaded node. // Just set the frame on the view. // NOTE: Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform. -#if DEBUG - // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. - ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); -#endif +//#if DEBUG +// // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. +// ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); +//#endif _view.frame = rect; } else { // We do have to set frame directly, but either the node isn't loaded or we're on a non-main thread. @@ -295,8 +298,17 @@ - (void)setFrame:(CGRect)rect - (void)setNeedsDisplay { - _bridge_prologue_write; - if (_hierarchyState & ASHierarchyStateRasterized) { + BOOL isRasterized = NO; + BOOL shouldApply = NO; + id viewOrLayer = nil; + { + _bridge_prologue_write; + isRasterized = _hierarchyState & ASHierarchyStateRasterized; + shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + viewOrLayer = _view ?: _layer; + } + + if (isRasterized) { ASPerformBlockOnMainThread(^{ // The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node // begins materializing the view / layer hierarchy (locking itself or a descendant) while this node walks up @@ -313,13 +325,13 @@ - (void)setNeedsDisplay [rasterizedContainerNode setNeedsDisplay]; }); } else { - BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); if (shouldApply) { // If not rasterized, and the node is loaded (meaning we certainly have a view or layer), send a // message to the view/layer first. This is because __setNeedsDisplay calls as scheduleNodeForDisplay, // which may call -displayIfNeeded. We want to ensure the needsDisplay flag is set now, and then cleared. - _messageToViewOrLayer(setNeedsDisplay); + [viewOrLayer setNeedsDisplay]; } else { + _bridge_prologue_write; [ASDisplayNodeGetPendingState(self) setNeedsDisplay]; } [self __setNeedsDisplay]; @@ -328,29 +340,59 @@ - (void)setNeedsDisplay - (void)setNeedsLayout { - _bridge_prologue_write; - BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + BOOL shouldApply = NO; + BOOL loaded = NO; + id viewOrLayer = nil; + { + _bridge_prologue_write; + shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + loaded = __loaded(self); + viewOrLayer = _view ?: _layer; + } + if (shouldApply) { // The node is loaded and we're on main. // Quite the opposite of setNeedsDisplay, we must call __setNeedsLayout before messaging // the view or layer to ensure that measurement and implicitly added subnodes have been handled. - - // Calling __setNeedsLayout while holding the property lock can cause deadlocks - _bridge_prologue_write_unlock; [self __setNeedsLayout]; - _bridge_prologue_write; - _messageToViewOrLayer(setNeedsLayout); - } else if (__loaded(self)) { + [viewOrLayer setNeedsLayout]; + } else if (loaded) { // The node is loaded but we're not on main. - // We will call [self __setNeedsLayout] when we apply - // the pending state. We need to call it on main if the node is loaded - // to support automatic subnode management. + // We will call [self __setNeedsLayout] when we apply the pending state. + // We need to call it on main if the node is loaded to support automatic subnode management. + _bridge_prologue_write; [ASDisplayNodeGetPendingState(self) setNeedsLayout]; } else { // The node is not loaded and we're not on main. - _bridge_prologue_write_unlock; [self __setNeedsLayout]; + } +} + +- (void)layoutIfNeeded +{ + BOOL shouldApply = NO; + BOOL loaded = NO; + id viewOrLayer = nil; + { + _bridge_prologue_write; + shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + loaded = __loaded(self); + viewOrLayer = _view ?: _layer; + } + + if (shouldApply) { + // The node is loaded and we're on main. + // Message the view or layer which in turn will call __layout on us (see -[_ASDisplayLayer layoutSublayers]). + [viewOrLayer layoutIfNeeded]; + } else if (loaded) { + // The node is loaded but we're not on main. + // We will call layoutIfNeeded on the view or layer when we apply the pending state. __layout will in turn be called on us (see -[_ASDisplayLayer layoutSublayers]). + // We need to call it on main if the node is loaded to support automatic subnode management. _bridge_prologue_write; + [ASDisplayNodeGetPendingState(self) layoutIfNeeded]; + } else { + // The node is not loaded and we're not on main. + [self __layout]; } } diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h similarity index 87% rename from AsyncDisplayKit/Private/ASDisplayNodeInternal.h rename to Source/Private/ASDisplayNodeInternal.h index 16720a6020..66e6f3b11a 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -14,16 +14,15 @@ // #import -#import "ASDisplayNode.h" -#import "ASThread.h" -#import "_ASTransitionContext.h" -#import "ASLayoutElement.h" -#import "ASLayoutTransition.h" -#import "ASEnvironment.h" -#import "ASObjectDescriptionHelpers.h" -#import "ASWeakSet.h" +#import +#import +#import +#import +#import +#import +#import -#import "ASDisplayNode+Beta.h" +NS_ASSUME_NONNULL_BEGIN @protocol _ASDisplayLayerDelegate; @class _ASDisplayLayer; @@ -35,7 +34,7 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); BOOL ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(ASDisplayNodeFlags flags); /// Get the pending view state for the node, creating one if needed. -_ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node); +_ASPendingState * ASDisplayNodeGetPendingState(ASDisplayNode * node); typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { @@ -44,7 +43,9 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1, ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2, ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3, - ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4 + ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4, + ASDisplayNodeMethodOverrideFetchData = 1 << 5, + ASDisplayNodeMethodOverrideClearFetchedData = 1 << 6 }; FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification; @@ -55,13 +56,11 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo #define TIME_DISPLAYNODE_OPS 0 // If you're using this information frequently, try: (DEBUG || PROFILE) -@interface ASDisplayNode () +@interface ASDisplayNode () { @package _ASPendingState *_pendingViewState; - // Protects access to _view, _layer, _pendingViewState, _subnodes, _supernode, and other properties which are accessed from multiple threads. - ASDN::RecursiveMutex __instanceLock__; UIView *_view; CALayer *_layer; @@ -107,21 +106,17 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo @protected ASDisplayNode * __weak _supernode; - + NSMutableArray *_subnodes; + ASLayoutElementStyle *_style; + ASPrimitiveTraitCollection _primitiveTraitCollection; std::atomic_uint _displaySentinel; - int32_t _transitionID; - BOOL _transitionInProgress; - // This is the desired contentsScale, not the scale at which the layer's contents should be displayed CGFloat _contentsScaleForDisplay; - ASEnvironmentState _environmentState; - UIEdgeInsets _hitTestSlop; - NSMutableArray *_subnodes; #if ASEVENTLOG_ENABLE ASEventLog *_eventLog; @@ -134,6 +129,9 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo NSTimeInterval _defaultLayoutTransitionDelay; UIViewAnimationOptions _defaultLayoutTransitionOptions; + int32_t _transitionID; + BOOL _transitionInProgress; + int32_t _pendingTransitionID; ASLayoutTransition *_pendingLayoutTransition; std::shared_ptr _calculatedDisplayNodeLayout; @@ -178,6 +176,13 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo NSTimeInterval _layoutComputationTotalTime; NSInteger _layoutComputationNumberOfPasses; +#if YOGA + YGNodeRef _yogaNode; + ASDisplayNode *_yogaParent; + NSMutableArray *_yogaChildren; + ASLayout *_yogaCalculatedLayout; +#endif + #if TIME_DISPLAYNODE_OPS @public NSTimeInterval _debugTimeToCreateView; @@ -190,18 +195,11 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo + (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node; /// The _ASDisplayLayer backing the node, if any. -@property (nonatomic, readonly, strong) _ASDisplayLayer *asyncLayer; +@property (nullable, nonatomic, readonly, strong) _ASDisplayLayer *asyncLayer; /// Bitmask to check which methods an object overrides. @property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides; -/// Thread safe way to access the bounds of the node -@property (nonatomic, assign) CGRect threadSafeBounds; - - -// Swizzle to extend the builtin functionality with custom logic -- (BOOL)__shouldLoadViewOrLayer; - /** * Invoked before a call to setNeedsLayout to the underlying view */ @@ -217,11 +215,6 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo */ - (void)__layout; -/* - * Internal method to set the supernode - */ -- (void)__setSupernode:(ASDisplayNode *)supernode; - /** * Internal method to add / replace / insert subnode and remove from supernode without checking if * node has automaticallyManagesSubnodes set to YES. @@ -231,6 +224,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo - (void)_insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below; - (void)_insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above; - (void)_insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx; +- (void)_removeFromSupernodeIfEqualTo:(ASDisplayNode *)supernode; - (void)_removeFromSupernode; // Private API for helper functions / unit tests. Use ASDisplayNodeDisableHierarchyNotifications() to control this. @@ -240,7 +234,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo - (void)__decrementVisibilityNotificationsDisabled; /// Helper method to summarize whether or not the node run through the display process -- (BOOL)__implementsDisplay; +- (BOOL)_implementsDisplay; /// Display the node's view/layer immediately on the current thread, bypassing the background thread rendering. Will be deprecated. - (void)displayImmediately; @@ -275,12 +269,20 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo * @param checkViewHierarchy If YES, and no supernode can be found, method will walk up from `self.view` to find a supernode. * If YES, this method must be called on the main thread and the node must not be layer-backed. */ -- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy; +- (nullable ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy; /** * Convenience method to access this node's trait collection struct. Externally, users should interact * with the trait collection via ASTraitCollection */ -- (ASEnvironmentTraitCollection)environmentTraitCollection; +- (ASPrimitiveTraitCollection)primitiveTraitCollection; + +/** + * This is a non-deprecated internal declaration of the property. Public declaration + * is in ASDisplayNode+Beta.h + */ +@property (nonatomic, assign) BOOL shouldRasterizeDescendants; @end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Private/ASDisplayNodeLayout.h b/Source/Private/ASDisplayNodeLayout.h similarity index 97% rename from AsyncDisplayKit/Private/ASDisplayNodeLayout.h rename to Source/Private/ASDisplayNodeLayout.h index a9a2d5a8a7..9852a2a22d 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeLayout.h +++ b/Source/Private/ASDisplayNodeLayout.h @@ -12,7 +12,7 @@ #pragma once -#import "ASDimension.h" +#import @class ASLayout; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeLayout.mm b/Source/Private/ASDisplayNodeLayout.mm similarity index 95% rename from AsyncDisplayKit/Private/ASDisplayNodeLayout.mm rename to Source/Private/ASDisplayNodeLayout.mm index 3e536bdf0e..697d0dea7a 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeLayout.mm +++ b/Source/Private/ASDisplayNodeLayout.mm @@ -10,7 +10,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#include "ASDisplayNodeLayout.h" +#import BOOL ASDisplayNodeLayout::isDirty() { diff --git a/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h b/Source/Private/ASEqualityHashHelpers.h similarity index 91% rename from AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h rename to Source/Private/ASEqualityHashHelpers.h index 20255c13ae..7079f78529 100644 --- a/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h +++ b/Source/Private/ASEqualityHashHelpers.h @@ -9,6 +9,8 @@ // #import +#import +#import #import @@ -16,7 +18,7 @@ // This is the Hash128to64 function from Google's cityhash (available // under the MIT License). We use it to reduce multiple 64 bit hashes // into a single hash. -inline uint64_t ASHashCombine(const uint64_t upper, const uint64_t lower) { +ASDISPLAYNODE_INLINE uint64_t ASHashCombine(const uint64_t upper, const uint64_t lower) { // Murmur-inspired hashing. const uint64_t kMul = 0x9ddfea08eb382d69ULL; uint64_t a = (lower ^ upper) * kMul; @@ -28,13 +30,13 @@ inline uint64_t ASHashCombine(const uint64_t upper, const uint64_t lower) { } #if __LP64__ -inline size_t ASHash64ToNative(uint64_t key) { +ASDISPLAYNODE_INLINE size_t ASHash64ToNative(uint64_t key) { return key; } #else // Thomas Wang downscaling hash function -inline size_t ASHash64ToNative(uint64_t key) { +ASDISPLAYNODE_INLINE size_t ASHash64ToNative(uint64_t key) { key = (~key) + (key << 18); key = key ^ (key >> 31); key = key * 21; @@ -45,6 +47,12 @@ inline size_t ASHash64ToNative(uint64_t key) { } #endif +NSUInteger ASHashFromCGPoint(const CGPoint point); + +NSUInteger ASHashFromCGSize(const CGSize size); + +NSUInteger ASHashFromCGRect(const CGRect rect); + NSUInteger ASIntegerArrayHash(const NSUInteger *subhashes, NSUInteger count); namespace AS { diff --git a/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.mm b/Source/Private/ASEqualityHashHelpers.mm similarity index 53% rename from AsyncDisplayKit/TextKit/ASEqualityHashHelpers.mm rename to Source/Private/ASEqualityHashHelpers.mm index 817777673b..3f98e4833e 100644 --- a/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.mm +++ b/Source/Private/ASEqualityHashHelpers.mm @@ -8,7 +8,24 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASEqualityHashHelpers.h" +#import + +#import + +NSUInteger ASHashFromCGPoint(const CGPoint point) +{ + return ASHash64ToNative(ASHashCombine(std::hash()(point.x), std::hash()(point.y))); +} + +NSUInteger ASHashFromCGSize(const CGSize size) +{ + return ASHash64ToNative(ASHashCombine(std::hash()(size.width), std::hash()(size.height))); +} + +NSUInteger ASHashFromCGRect(const CGRect rect) +{ + return ASHashFromCGPoint(rect.origin) + ASHashFromCGSize(rect.size); +} NSUInteger ASIntegerArrayHash(const NSUInteger *subhashes, NSUInteger count) { diff --git a/Source/Private/ASIGListAdapterBasedDataSource.h b/Source/Private/ASIGListAdapterBasedDataSource.h new file mode 100644 index 0000000000..6eb8ed81de --- /dev/null +++ b/Source/Private/ASIGListAdapterBasedDataSource.h @@ -0,0 +1,28 @@ +// +// ASIGListAdapterBasedDataSource.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/19/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#if AS_IG_LIST_KIT + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED +@interface ASIGListAdapterBasedDataSource : NSObject + +- (instancetype)initWithListAdapter:(IGListAdapter *)listAdapter; + +@end + +#endif + +NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASIGListAdapterBasedDataSource.m b/Source/Private/ASIGListAdapterBasedDataSource.m new file mode 100644 index 0000000000..1ed2582532 --- /dev/null +++ b/Source/Private/ASIGListAdapterBasedDataSource.m @@ -0,0 +1,321 @@ +// +// ASIGListAdapterBasedDataSource.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/19/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#if AS_IG_LIST_KIT + +#import "ASIGListAdapterBasedDataSource.h" +#import +#import + +typedef IGListSectionController ASIGSectionController; + +/// The optional methods that a class implements from ASSectionController. +/// Note: Bitfields are not supported by NSValue so we can't use them. +typedef struct { + BOOL sizeRangeForItem; + BOOL shouldBatchFetch; + BOOL beginBatchFetchWithContext; +} ASSectionControllerOverrides; + +/// The optional methods that a class implements from ASSupplementaryNodeSource. +/// Note: Bitfields are not supported by NSValue so we can't use them. +typedef struct { + BOOL sizeRangeForSupplementary; +} ASSupplementarySourceOverrides; + +@protocol ASIGSupplementaryNodeSource +@end + +@interface ASIGListAdapterBasedDataSource () +@property (nonatomic, weak, readonly) IGListAdapter *listAdapter; +@property (nonatomic, readonly) id delegate; +@property (nonatomic, readonly) id dataSource; + +/** + * The section controller that we will forward beginBatchFetchWithContext: to. + * Since shouldBatchFetch: is called on main, we capture the last section controller in there, + * and then we use it and clear it in beginBatchFetchWithContext: (on default queue). + * + * It is safe to use it without a lock in this limited way, since those two methods will + * never execute in parallel. + */ +@property (nonatomic, weak) ASIGSectionController *sectionControllerForBatchFetching; +@end + +@implementation ASIGListAdapterBasedDataSource + +- (instancetype)initWithListAdapter:(IGListAdapter *)listAdapter +{ + if (self = [super init]) { +#if IG_LIST_COLLECTION_VIEW + [ASIGListAdapterBasedDataSource setASCollectionViewSuperclass]; +#endif + [ASIGListAdapterBasedDataSource configureUpdater:listAdapter.updater]; + + ASDisplayNodeAssert([listAdapter conformsToProtocol:@protocol(UICollectionViewDataSource)], @"Expected IGListAdapter to conform to UICollectionViewDataSource."); + ASDisplayNodeAssert([listAdapter conformsToProtocol:@protocol(UICollectionViewDelegateFlowLayout)], @"Expected IGListAdapter to conform to UICollectionViewDelegateFlowLayout."); + _listAdapter = listAdapter; + } + return self; +} + +- (id)dataSource +{ + return (id)_listAdapter; +} + +- (id)delegate +{ + return (id)_listAdapter; +} + +#pragma mark - ASCollectionDelegate + +- (void)collectionNode:(ASCollectionNode *)collectionNode didSelectItemAtIndexPath:(NSIndexPath *)indexPath +{ + [self.delegate collectionView:collectionNode.view didSelectItemAtIndexPath:indexPath]; +} + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + [self.delegate scrollViewDidScroll:scrollView]; +} + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView +{ + [self.delegate scrollViewWillBeginDragging:scrollView]; +} + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate +{ + [self.delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; +} + +- (BOOL)shouldBatchFetchForCollectionNode:(ASCollectionNode *)collectionNode +{ + NSInteger sectionCount = [self numberOfSectionsInCollectionNode:collectionNode]; + if (sectionCount == 0) { + return NO; + } + + // If they implement shouldBatchFetch, call it. Otherwise, just say YES if they implement beginBatchFetch. + ASIGSectionController *ctrl = [self sectionControllerForSection:sectionCount - 1]; + ASSectionControllerOverrides o = [ASIGListAdapterBasedDataSource overridesForSectionControllerClass:ctrl.class]; + BOOL result = (o.shouldBatchFetch ? [ctrl shouldBatchFetch] : o.beginBatchFetchWithContext); + if (result) { + self.sectionControllerForBatchFetching = ctrl; + } + return result; +} + +- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context +{ + ASIGSectionController *ctrl = self.sectionControllerForBatchFetching; + self.sectionControllerForBatchFetching = nil; + [ctrl beginBatchFetchWithContext:context]; +} + +/** + * Note: It is not documented that ASCollectionNode will forward these UIKit delegate calls if they are implemented. + * It is not considered harmful to do so, and adding them to documentation will confuse most users, who should + * instead using the ASCollectionDelegate callbacks. + */ +#pragma mark - ASCollectionDelegateInterop + +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath +{ + [self.delegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; +} + +- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath +{ + [self.delegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath]; +} + +#pragma mark - ASCollectionDelegateFlowLayout + +- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForHeaderInSection:(NSInteger)section +{ + id src = [self supplementaryElementSourceForSection:section]; + if ([ASIGListAdapterBasedDataSource overridesForSupplementarySourceClass:[src class]].sizeRangeForSupplementary) { + return [src sizeRangeForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndex:0]; + } else { + return ASSizeRangeZero; + } +} + +- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section +{ + id src = [self supplementaryElementSourceForSection:section]; + if ([ASIGListAdapterBasedDataSource overridesForSupplementarySourceClass:[src class]].sizeRangeForSupplementary) { + return [src sizeRangeForSupplementaryElementOfKind:UICollectionElementKindSectionFooter atIndex:0]; + } else { + return ASSizeRangeZero; + } +} + +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section +{ + return [self.delegate collectionView:collectionView layout:collectionViewLayout insetForSectionAtIndex:section]; +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section +{ + return [self.delegate collectionView:collectionView layout:collectionViewLayout minimumLineSpacingForSectionAtIndex:section]; +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section +{ + return [self.delegate collectionView:collectionView layout:collectionViewLayout minimumInteritemSpacingForSectionAtIndex:section]; +} + +#pragma mark - ASCollectionDataSource + +- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section +{ + return [self.dataSource collectionView:collectionNode.view numberOfItemsInSection:section]; +} + +- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode +{ + return [self.dataSource numberOfSectionsInCollectionView:collectionNode.view]; +} + +- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath +{ + return [[self sectionControllerForSection:indexPath.section] nodeBlockForItemAtIndex:indexPath.item]; +} + +- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath +{ + ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section]; + if ([ASIGListAdapterBasedDataSource overridesForSectionControllerClass:ctrl.class].sizeRangeForItem) { + return [ctrl sizeRangeForItemAtIndex:indexPath.item]; + } else { + return ASSizeRangeUnconstrained; + } +} + +- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + return [[self supplementaryElementSourceForSection:indexPath.section] nodeBlockForSupplementaryElementOfKind:kind atIndex:indexPath.item]; +} + +- (NSArray *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section +{ + return [[self supplementaryElementSourceForSection:section] supportedElementKinds]; +} + +#pragma mark - ASCollectionDataSourceInterop + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath +{ + return [self.dataSource collectionView:collectionView cellForItemAtIndexPath:indexPath]; +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + return [self.dataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; +} + ++ (BOOL)dequeuesCellsForNodeBackedItems +{ + return YES; +} + +#pragma mark - Helpers + +- (id)supplementaryElementSourceForSection:(NSInteger)section +{ + ASIGSectionController *ctrl = [self sectionControllerForSection:section]; + id src = (id)ctrl.supplementaryViewSource; + ASDisplayNodeAssert(src == nil || [src conformsToProtocol:@protocol(ASSupplementaryNodeSource)], @"Supplementary view source should conform to %@", NSStringFromProtocol(@protocol(ASSupplementaryNodeSource))); + return src; +} + +- (ASIGSectionController *)sectionControllerForSection:(NSInteger)section +{ + id object = [_listAdapter objectAtSection:section]; + ASIGSectionController *ctrl = (ASIGSectionController *)[_listAdapter sectionControllerForObject:object]; + ASDisplayNodeAssert([ctrl conformsToProtocol:@protocol(ASSectionController)], @"Expected section controller to conform to %@. Controller: %@", NSStringFromProtocol(@protocol(ASSectionController)), ctrl); + return ctrl; +} + +/// If needed, set ASCollectionView's superclass to IGListCollectionView (IGListKit < 3.0). +#if IG_LIST_COLLECTION_VIEW ++ (void)setASCollectionViewSuperclass +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + class_setSuperclass([ASCollectionView class], [IGListCollectionView class]); + }); +#pragma clang diagnostic pop +} +#endif + +/// Ensure updater won't call reloadData on us. ++ (void)configureUpdater:(id)updater +{ + // Cast to NSObject will be removed after https://github.com/Instagram/IGListKit/pull/435 + if ([(id)updater isKindOfClass:[IGListAdapterUpdater class]]) { + [(IGListAdapterUpdater *)updater setAllowsBackgroundReloading:NO]; + } else { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"WARNING: Use of non-%@ updater with AsyncDisplayKit is discouraged. Updater: %@", NSStringFromClass([IGListAdapterUpdater class]), updater); + }); + } +} + ++ (ASSupplementarySourceOverrides)overridesForSupplementarySourceClass:(Class)c +{ + static NSCache *cache; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + cache = [[NSCache alloc] init]; + }); + NSValue *obj = [cache objectForKey:c]; + ASSupplementarySourceOverrides o; + if (obj == nil) { + o.sizeRangeForSupplementary = [c instancesRespondToSelector:@selector(sizeRangeForSupplementaryElementOfKind:atIndex:)]; + obj = [NSValue valueWithBytes:&o objCType:@encode(ASSupplementarySourceOverrides)]; + [cache setObject:obj forKey:c]; + } else { + [obj getValue:&o]; + } + return o; +} + ++ (ASSectionControllerOverrides)overridesForSectionControllerClass:(Class)c +{ + static NSCache *cache; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + cache = [[NSCache alloc] init]; + }); + NSValue *obj = [cache objectForKey:c]; + ASSectionControllerOverrides o; + if (obj == nil) { + o.sizeRangeForItem = [c instancesRespondToSelector:@selector(sizeRangeForItemAtIndex:)]; + o.beginBatchFetchWithContext = [c instancesRespondToSelector:@selector(beginBatchFetchWithContext:)]; + o.shouldBatchFetch = [c instancesRespondToSelector:@selector(shouldBatchFetch)]; + obj = [NSValue valueWithBytes:&o objCType:@encode(ASSectionControllerOverrides)]; + [cache setObject:obj forKey:c]; + } else { + [obj getValue:&o]; + } + return o; +} + +@end + +#endif // AS_IG_LIST_KIT diff --git a/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h b/Source/Private/ASImageNode+AnimatedImagePrivate.h similarity index 85% rename from AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h rename to Source/Private/ASImageNode+AnimatedImagePrivate.h index 6748f1af65..bdd0a15e49 100644 --- a/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h +++ b/Source/Private/ASImageNode+AnimatedImagePrivate.h @@ -10,7 +10,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASThread.h" +#import extern NSString *const ASAnimatedImageDefaultRunLoopMode; @@ -32,6 +32,12 @@ extern NSString *const ASAnimatedImageDefaultRunLoopMode; @end +@interface ASImageNode (AnimatedImagePrivate) + +- (void)_locked_setAnimatedImage:(id )animatedImage; + +@end + @interface ASImageNode (AnimatedImageInvalidation) diff --git a/AsyncDisplayKit/Private/ASImageNode+CGExtras.h b/Source/Private/ASImageNode+CGExtras.h similarity index 99% rename from AsyncDisplayKit/Private/ASImageNode+CGExtras.h rename to Source/Private/ASImageNode+CGExtras.h index a2808bbb6f..3987aa492d 100644 --- a/AsyncDisplayKit/Private/ASImageNode+CGExtras.h +++ b/Source/Private/ASImageNode+CGExtras.h @@ -8,10 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #import -#include - ASDISPLAYNODE_EXTERN_C_BEGIN diff --git a/AsyncDisplayKit/Private/ASImageNode+CGExtras.m b/Source/Private/ASImageNode+CGExtras.m similarity index 98% rename from AsyncDisplayKit/Private/ASImageNode+CGExtras.m rename to Source/Private/ASImageNode+CGExtras.m index 34d4582d15..2e4aaf85a2 100644 --- a/AsyncDisplayKit/Private/ASImageNode+CGExtras.m +++ b/Source/Private/ASImageNode+CGExtras.m @@ -8,7 +8,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASImageNode+CGExtras.h" +#import + #import // TODO rewrite these to be closer to the intended use -- take UIViewContentMode as param, CGRect destinationBounds, CGSize sourceSize. @@ -66,7 +67,7 @@ void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, } // If fitting the desired aspect ratio to the image size actually results in a larger buffer, use the input values. - // However, if there is a pixel savings (e.g. we would have to upscale the image), overwrite the function arguments. + // However, if there is a pixel savings (e.g. we would have to upscale the image), override the function arguments. if (CGSizeEqualToSize(CGSizeZero, forcedSize) == NO) { destinationWidth = (size_t)round(forcedSize.width); destinationHeight = (size_t)round(forcedSize.height); diff --git a/Source/Private/ASImageNode+Private.h b/Source/Private/ASImageNode+Private.h new file mode 100644 index 0000000000..e0880ba5a7 --- /dev/null +++ b/Source/Private/ASImageNode+Private.h @@ -0,0 +1,16 @@ +// +// ASImageNode+Private.h +// AsyncDisplayKit +// +// Created by Michael Schneider on 3/20/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#pragma once + +@interface ASImageNode (Private) + +- (void)_locked_setImage:(UIImage *)image; +- (UIImage *)_locked_Image; + +@end diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/Source/Private/ASInternalHelpers.h similarity index 97% rename from AsyncDisplayKit/Private/ASInternalHelpers.h rename to Source/Private/ASInternalHelpers.h index ec650bbadc..87eb24f2f0 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/Source/Private/ASInternalHelpers.h @@ -8,6 +8,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import "ASAvailability.h" + #import #import @@ -45,7 +47,7 @@ CGFloat ASRoundPixelValue(CGFloat f); BOOL ASClassRequiresMainThreadDeallocation(Class _Nullable c); -Class _Nullable ASGetClassFromType(const char *type); +Class _Nullable ASGetClassFromType(const char * _Nullable type); ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.m b/Source/Private/ASInternalHelpers.m similarity index 88% rename from AsyncDisplayKit/Private/ASInternalHelpers.m rename to Source/Private/ASInternalHelpers.m index 4a78f14d65..385b33c32e 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.m +++ b/Source/Private/ASInternalHelpers.m @@ -8,14 +8,16 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASInternalHelpers.h" -#import "ASRunLoopQueue.h" +#import -#import +#import -#import "ASThread.h" +#import #import +#import +#import + BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector) { if (superclass == subclass) return NO; // Even if the class implements the selector, it doesn't override itself. @@ -84,19 +86,19 @@ void ASPerformBackgroundDeallocation(id object) [[ASDeallocQueue sharedDeallocationQueue] releaseObjectInBackground:object]; } -BOOL ASClassRequiresMainThreadDeallocation(Class class) +BOOL ASClassRequiresMainThreadDeallocation(Class c) { - if (class == [UIImage class] || class == [UIColor class]) { + if (c == [UIImage class] || c == [UIColor class]) { return NO; } - - if ([class isSubclassOfClass:[UIResponder class]] - || [class isSubclassOfClass:[CALayer class]] - || [class isSubclassOfClass:[UIGestureRecognizer class]]) { + + if ([c isSubclassOfClass:[UIResponder class]] + || [c isSubclassOfClass:[CALayer class]] + || [c isSubclassOfClass:[UIGestureRecognizer class]]) { return YES; } - const char *name = class_getName(class); + const char *name = class_getName(c); if (strncmp(name, "UI", 2) == 0 || strncmp(name, "AV", 2) == 0 || strncmp(name, "CA", 2) == 0) { return YES; } @@ -104,10 +106,10 @@ BOOL ASClassRequiresMainThreadDeallocation(Class class) return NO; } -Class _Nullable ASGetClassFromType(const char *type) +Class _Nullable ASGetClassFromType(const char * _Nullable type) { // Class types all start with @" - if (strncmp(type, "@\"", 2) != 0) { + if (type == NULL || strncmp(type, "@\"", 2) != 0) { return nil; } diff --git a/AsyncDisplayKit/Private/ASLayoutTransition.h b/Source/Private/ASLayoutTransition.h similarity index 68% rename from AsyncDisplayKit/Private/ASLayoutTransition.h rename to Source/Private/ASLayoutTransition.h index db2e764790..f3e384e901 100644 --- a/AsyncDisplayKit/Private/ASLayoutTransition.h +++ b/Source/Private/ASLayoutTransition.h @@ -10,16 +10,41 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASDimension.h" -#import "_ASTransitionContext.h" -#import "ASDisplayNodeLayout.h" +#import +#import +#import +#import + +#import +#import #import NS_ASSUME_NONNULL_BEGIN -@class ASDisplayNode; +#pragma mark - ASLayoutElementTransition + +/** + * Extend the layout element protocol to check if a the element can layout asynchronously. + */ +@protocol ASLayoutElementTransition + +/** + * @abstract Returns if the layoutElement can be used to layout in an asynchronous way on a background thread. + */ +@property (nonatomic, assign, readonly) BOOL canLayoutAsynchronous; + +@end + +@interface ASDisplayNode () +@end +@interface ASLayoutSpec () +@end + + +#pragma mark - ASLayoutTransition +AS_SUBCLASSING_RESTRICTED @interface ASLayoutTransition : NSObject <_ASTransitionContextLayoutDelegate> /** diff --git a/AsyncDisplayKit/Private/ASLayoutTransition.mm b/Source/Private/ASLayoutTransition.mm similarity index 86% rename from AsyncDisplayKit/Private/ASLayoutTransition.mm rename to Source/Private/ASLayoutTransition.mm index 9c8df8cbd2..0d9af91f24 100644 --- a/AsyncDisplayKit/Private/ASLayoutTransition.mm +++ b/Source/Private/ASLayoutTransition.mm @@ -10,16 +10,19 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASLayoutTransition.h" +#import -#import "ASDisplayNodeInternal.h" -#import "ASLayout.h" +#import +#import + +#import +#import // Required for _insertSubnode... / _removeFromSupernode. #import #import -#import "NSArray+Diffing.h" -#import "ASEqualityHelpers.h" +#import +#import /** * Search the whole layout stack if at least one layout has a layoutElement object that can not be layed out asynchronous. @@ -34,7 +37,10 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { layout = queue.front(); queue.pop(); - if (layout.layoutElement.canLayoutAsynchronous == NO) { +#if DEBUG + ASDisplayNodeCAssert([layout.layoutElement conformsToProtocol:@protocol(ASLayoutElementTransition)], @"ASLayoutElement in a layout transition needs to conforms to the ASLayoutElementTransition protocol."); +#endif + if (((id)layout.layoutElement).canLayoutAsynchronous == NO) { return NO; } @@ -119,7 +125,10 @@ - (void)applySubnodeRemovals ASDisplayNodeLogEvent(_node, @"removeSubnodes: %@", _removedSubnodes); for (ASDisplayNode *subnode in _removedSubnodes) { - [subnode _removeFromSupernode]; + // In this case we should only remove the subnode if it's still a subnode of the _node that executes a layout transition. + // It can happen that a node already did a layout transition and added this subnode, in this case the subnode + // would be removed from the new node instead of _node + [subnode _removeFromSupernodeIfEqualTo:_node]; } } @@ -233,7 +242,7 @@ - (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSize if (idx > lastIndex) { break; } if (idx >= firstIndex && [indexes containsIndex:idx]) { ASDisplayNode *node = (ASDisplayNode *)sublayout.layoutElement; - ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts"); + ASDisplayNodeCAssert(node, @"ASDisplayNode was deallocated before it was added to a subnode. It's likely the case that you use automatically manages subnodes and allocate a ASDisplayNode in layoutSpecThatFits: and don't have any strong reference to it."); // Ignore the odd case in which a non-node sublayout is accessed and the type cast fails if (node != nil) { BOOL notFiltered = (filteredNodes == nil || [filteredNodes indexOfObjectIdenticalTo:node] == NSNotFound); diff --git a/Source/Private/ASMutableElementMap.h b/Source/Private/ASMutableElementMap.h new file mode 100644 index 0000000000..221353e573 --- /dev/null +++ b/Source/Private/ASMutableElementMap.h @@ -0,0 +1,52 @@ +// +// ASMutableElementMap.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/23/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class ASSection, ASCollectionElement; + +/** + * This mutable version will be removed in the future. It's only here now to keep the diff small + * as we port data controller to use ASElementMap. + */ +AS_SUBCLASSING_RESTRICTED +@interface ASMutableElementMap : NSObject + +- (instancetype)init __unavailable; + +- (instancetype)initWithSections:(NSArray *)sections items:(ASCollectionElementTwoDimensionalArray *)items supplementaryElements:(ASSupplementaryElementDictionary *)supplementaryElements; + +- (void)insertSection:(ASSection *)section atIndex:(NSInteger)index; + +- (void)removeAllSectionContexts; + +/// Only modifies the array of ASSection * objects +- (void)removeSectionContextsAtIndexes:(NSIndexSet *)indexes; + +- (void)removeAllElements; + +- (void)removeItemsAtIndexPaths:(NSArray *)indexPaths; + +- (void)removeSectionsOfItems:(NSIndexSet *)itemSections; + +- (void)removeSupplementaryElementsInSections:(NSIndexSet *)sections; + +- (void)insertEmptySectionsOfItemsAtIndexes:(NSIndexSet *)sections; + +- (void)insertElement:(ASCollectionElement *)element atIndexPath:(NSIndexPath *)indexPath; + +@end + +@interface ASElementMap (MutableCopying) +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASMutableElementMap.m b/Source/Private/ASMutableElementMap.m new file mode 100644 index 0000000000..b818d531c0 --- /dev/null +++ b/Source/Private/ASMutableElementMap.m @@ -0,0 +1,114 @@ +// +// ASMutableElementMap.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/23/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "ASMutableElementMap.h" + +#import +#import +#import +#import +#import + +typedef NSMutableArray *> ASMutableCollectionElementTwoDimensionalArray; + +typedef NSMutableDictionary *> ASMutableSupplementaryElementDictionary; + +@implementation ASMutableElementMap { + ASMutableSupplementaryElementDictionary *_supplementaryElements; + NSMutableArray *_sections; + ASMutableCollectionElementTwoDimensionalArray *_sectionsOfItems; +} + +- (instancetype)initWithSections:(NSArray *)sections items:(ASCollectionElementTwoDimensionalArray *)items supplementaryElements:(ASSupplementaryElementDictionary *)supplementaryElements +{ + if (self = [super init]) { + _sections = [sections mutableCopy]; + _sectionsOfItems = (id)ASTwoDimensionalArrayDeepMutableCopy(items); + _supplementaryElements = [ASMutableElementMap deepMutableCopyOfElementsDictionary:supplementaryElements]; + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone +{ + return [[ASElementMap alloc] initWithSections:_sections items:_sectionsOfItems supplementaryElements:_supplementaryElements]; +} + +- (void)removeAllSectionContexts +{ + [_sections removeAllObjects]; +} + +- (void)insertSection:(ASSection *)section atIndex:(NSInteger)index +{ + [_sections insertObject:section atIndex:index]; +} + +- (void)removeItemsAtIndexPaths:(NSArray *)indexPaths +{ + ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(_sectionsOfItems, indexPaths); +} + +- (void)removeSectionContextsAtIndexes:(NSIndexSet *)indexes +{ + [_sections removeObjectsAtIndexes:indexes]; +} + +- (void)removeAllElements +{ + [_sectionsOfItems removeAllObjects]; + [_supplementaryElements removeAllObjects]; +} + +- (void)removeSectionsOfItems:(NSIndexSet *)itemSections +{ + [_sectionsOfItems removeObjectsAtIndexes:itemSections]; +} + +- (void)removeSupplementaryElementsInSections:(NSIndexSet *)sections +{ + [_supplementaryElements enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSMutableDictionary * _Nonnull supplementariesForKind, BOOL * _Nonnull stop) { + [supplementariesForKind removeObjectsForKeys:[sections as_filterIndexPathsBySection:supplementariesForKind]]; + }]; +} + +- (void)insertEmptySectionsOfItemsAtIndexes:(NSIndexSet *)sections +{ + [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + [_sectionsOfItems insertObject:[NSMutableArray array] atIndex:idx]; + }]; +} + +- (void)insertElement:(ASCollectionElement *)element atIndexPath:(NSIndexPath *)indexPath +{ + NSString *kind = element.supplementaryElementKind; + if (kind == nil) { + [_sectionsOfItems[indexPath.section] insertObject:element atIndex:indexPath.item]; + } else { + NSMutableDictionary *supplementariesForKind = _supplementaryElements[kind]; + if (supplementariesForKind == nil) { + supplementariesForKind = [NSMutableDictionary dictionary]; + _supplementaryElements[kind] = supplementariesForKind; + } + supplementariesForKind[indexPath] = element; + } +} + +#pragma mark - Helpers + ++ (ASMutableSupplementaryElementDictionary *)deepMutableCopyOfElementsDictionary:(ASSupplementaryElementDictionary *)originalDict +{ + NSMutableDictionary *deepCopy = [NSMutableDictionary dictionaryWithCapacity:originalDict.count]; + [originalDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSDictionary * _Nonnull obj, BOOL * _Nonnull stop) { + deepCopy[key] = [obj mutableCopy]; + }]; + + return deepCopy; +} + +@end diff --git a/AsyncDisplayKit/Private/ASPendingStateController.h b/Source/Private/ASPendingStateController.h similarity index 95% rename from AsyncDisplayKit/Private/ASPendingStateController.h rename to Source/Private/ASPendingStateController.h index fef12758ca..0399ab9f20 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.h +++ b/Source/Private/ASPendingStateController.h @@ -11,6 +11,7 @@ // #import +#import @class ASDisplayNode; @@ -24,6 +25,7 @@ NS_ASSUME_NONNULL_BEGIN This controller will enqueue run-loop events to flush changes but if you need them flushed now you can call `flush` from the main thread. */ +AS_SUBCLASSING_RESTRICTED @interface ASPendingStateController : NSObject + (ASPendingStateController *)sharedInstance; diff --git a/AsyncDisplayKit/Private/ASPendingStateController.mm b/Source/Private/ASPendingStateController.mm similarity index 89% rename from AsyncDisplayKit/Private/ASPendingStateController.mm rename to Source/Private/ASPendingStateController.mm index e02e1e5ee0..371a531447 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.mm +++ b/Source/Private/ASPendingStateController.mm @@ -10,10 +10,10 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASPendingStateController.h" -#import "ASThread.h" -#import "ASWeakSet.h" -#import "ASDisplayNodeInternal.h" +#import +#import +#import +#import // Required for -applyPendingViewState; consider moving this to +FrameworkPrivate @interface ASPendingStateController() { diff --git a/Source/Private/ASRectTable.h b/Source/Private/ASRectTable.h new file mode 100644 index 0000000000..3617f44b17 --- /dev/null +++ b/Source/Private/ASRectTable.h @@ -0,0 +1,64 @@ +// +// ASRectTable.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/24/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * An alias for an NSMapTable created to store rects. + * + * You should not call -objectForKey:, -setObject:forKey:, or -allObjects + * on these objects. + */ +typedef NSMapTable ASRectTable; + +/** + * A category for creating & using map tables meant for storing CGRects. + * + * This category is private, so name collisions are not worth worrying about. + */ +@interface NSMapTable (ASRectTableMethods) + +/** + * Creates a new rect table with (NSMapTableStrongMemory | NSMapTableObjectPointerPersonality) for keys. + */ ++ (ASRectTable *)rectTableForStrongObjectPointers; + +/** + * Creates a new rect table with (NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) for keys. + */ ++ (ASRectTable *)rectTableForWeakObjectPointers; + +/** + * Retrieves the rect for a given key, or CGRectNull if the key is not found. + * + * @param key An object to lookup the rect for. + */ +- (CGRect)rectForKey:(KeyType)key; + +/** + * Sets the given rect for the associated key. + * + * @param rect The rect to store as value. + * @param key The key to use for the rect. + */ +- (void)setRect:(CGRect)rect forKey:(KeyType)key; + +/** + * Removes the rect for the given key, if one exists. + * + * @param key The key to remove. + */ +- (void)removeRectForKey:(KeyType)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASRectTable.m b/Source/Private/ASRectTable.m new file mode 100644 index 0000000000..1bd32fb53f --- /dev/null +++ b/Source/Private/ASRectTable.m @@ -0,0 +1,71 @@ +// +// ASRectTable.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/24/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "ASRectTable.h" + +__attribute__((const)) +static NSUInteger ASRectSize(const void *ptr) +{ + return sizeof(CGRect); +} + +@implementation NSMapTable (ASRectTableMethods) + ++ (instancetype)rectTableWithKeyPointerFunctions:(NSPointerFunctions *)keyFuncs +{ + static NSPointerFunctions *cgRectFuncs; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + cgRectFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStructPersonality | NSPointerFunctionsCopyIn | NSPointerFunctionsMallocMemory]; + cgRectFuncs.sizeFunction = &ASRectSize; + }); + + return [[NSMapTable alloc] initWithKeyPointerFunctions:keyFuncs valuePointerFunctions:cgRectFuncs capacity:0]; +} + ++ (instancetype)rectTableForStrongObjectPointers +{ + static NSPointerFunctions *strongObjectPointerFuncs; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + strongObjectPointerFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality]; + }); + return [self rectTableWithKeyPointerFunctions:strongObjectPointerFuncs]; +} + ++ (instancetype)rectTableForWeakObjectPointers +{ + static NSPointerFunctions *weakObjectPointerFuncs; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + weakObjectPointerFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality]; + }); + return [self rectTableWithKeyPointerFunctions:weakObjectPointerFuncs]; +} + +- (CGRect)rectForKey:(id)key +{ + CGRect *ptr = (__bridge CGRect *)[self objectForKey:key]; + if (ptr == NULL) { + return CGRectNull; + } + return *ptr; +} + +- (void)setRect:(CGRect)rect forKey:(id)key +{ + __unsafe_unretained id obj = (__bridge id)▭ + [self setObject:obj forKey:key]; +} + +- (void)removeRectForKey:(id)key +{ + [self removeObjectForKey:key]; +} + +@end diff --git a/Source/Private/ASResponderChainEnumerator.h b/Source/Private/ASResponderChainEnumerator.h new file mode 100644 index 0000000000..4e292edab8 --- /dev/null +++ b/Source/Private/ASResponderChainEnumerator.h @@ -0,0 +1,28 @@ +// +// ASResponderChainEnumerator.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/13/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED +@interface ASResponderChainEnumerator : NSEnumerator + +- (instancetype)initWithResponder:(UIResponder *)responder; + +@end + +@interface UIResponder (ASResponderChainEnumerator) + +- (ASResponderChainEnumerator *)asdk_responderChainEnumerator; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASResponderChainEnumerator.m b/Source/Private/ASResponderChainEnumerator.m new file mode 100644 index 0000000000..709443dc3d --- /dev/null +++ b/Source/Private/ASResponderChainEnumerator.m @@ -0,0 +1,44 @@ +// +// ASResponderChainEnumerator.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/13/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "ASResponderChainEnumerator.h" +#import + +@implementation ASResponderChainEnumerator { + UIResponder *_currentResponder; +} + +- (instancetype)initWithResponder:(UIResponder *)responder +{ + ASDisplayNodeAssertMainThread(); + if (self = [super init]) { + _currentResponder = responder; + } + return self; +} + +#pragma mark - NSEnumerator + +- (id)nextObject +{ + ASDisplayNodeAssertMainThread(); + id result = [_currentResponder nextResponder]; + _currentResponder = result; + return result; +} + +@end + +@implementation UIResponder (ASResponderChainEnumerator) + +- (NSEnumerator *)asdk_responderChainEnumerator +{ + return [[ASResponderChainEnumerator alloc] initWithResponder:self]; +} + +@end diff --git a/AsyncDisplayKit/Private/ASSection.h b/Source/Private/ASSection.h similarity index 100% rename from AsyncDisplayKit/Private/ASSection.h rename to Source/Private/ASSection.h diff --git a/AsyncDisplayKit/Private/ASSection.m b/Source/Private/ASSection.m similarity index 88% rename from AsyncDisplayKit/Private/ASSection.m rename to Source/Private/ASSection.m index 43c0c82e2a..6e743454ab 100644 --- a/AsyncDisplayKit/Private/ASSection.m +++ b/Source/Private/ASSection.m @@ -10,8 +10,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASSection.h" -#import "ASSectionContext.h" +#import +#import @implementation ASSection diff --git a/AsyncDisplayKit/Private/ASTableView+Undeprecated.h b/Source/Private/ASTableView+Undeprecated.h similarity index 98% rename from AsyncDisplayKit/Private/ASTableView+Undeprecated.h rename to Source/Private/ASTableView+Undeprecated.h index 3af9fd838b..6ef10380da 100644 --- a/AsyncDisplayKit/Private/ASTableView+Undeprecated.h +++ b/Source/Private/ASTableView+Undeprecated.h @@ -6,7 +6,10 @@ // Copyright © 2016 Facebook. All rights reserved. // -#import +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/Private/ASTwoDimensionalArrayUtils.h b/Source/Private/ASTwoDimensionalArrayUtils.h new file mode 100644 index 0000000000..e338569934 --- /dev/null +++ b/Source/Private/ASTwoDimensionalArrayUtils.h @@ -0,0 +1,53 @@ +// +// ASTwoDimensionalArrayUtils.h +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import + + +NS_ASSUME_NONNULL_BEGIN + +/** + * Helper class for operation on two-dimensional array, where the objects of the root array are each arrays + */ + +ASDISPLAYNODE_EXTERN_C_BEGIN + +/** + * Deep mutable copy of an array that contains arrays, which contain objects. It will go one level deep into the array to copy. + * This method is substantially faster than the generalized version, e.g. about 10x faster, so use it whenever it fits the need. + */ +extern NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) AS_WARN_UNUSED_RESULT; + +/** + * Delete the elements of the mutable two-dimensional array at given index paths – sorted in descending order! + */ +extern void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths); + +/** + * Return all the index paths of a two-dimensional array, in ascending order. + */ +extern NSArray *ASIndexPathsForTwoDimensionalArray(NSArray* twoDimensionalArray) AS_WARN_UNUSED_RESULT; + +/** + * Return all the elements of a two-dimensional array, in ascending order. + */ +extern NSArray *ASElementsInTwoDimensionalArray(NSArray* twoDimensionalArray) AS_WARN_UNUSED_RESULT; + +/** + * Attempt to get the object at the given index path. Returns @c nil if the index path is out of bounds. + */ +extern id _Nullable ASGetElementInTwoDimensionalArray(NSArray *array, NSIndexPath *indexPath) AS_WARN_UNUSED_RESULT; + + +ASDISPLAYNODE_EXTERN_C_END + +NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASTwoDimensionalArrayUtils.m b/Source/Private/ASTwoDimensionalArrayUtils.m new file mode 100644 index 0000000000..163f0b66bd --- /dev/null +++ b/Source/Private/ASTwoDimensionalArrayUtils.m @@ -0,0 +1,109 @@ +// +// ASTwoDimensionalArrayUtils.m +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import +#import + +// Import UIKit to get [NSIndexPath indexPathForItem:inSection:] which uses +// tagged pointers. +#import + +#pragma mark - Public Methods + +NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) +{ + NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; + NSInteger i = 0; + for (NSArray *subarray in array) { + ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); + newArray[i++] = [subarray mutableCopy]; + } + return newArray; +} + +void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) +{ + if (indexPaths.count == 0) { + return; + } + +#if ASDISPLAYNODE_ASSERTIONS_ENABLED + NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(asdk_inverseCompare:)]; + ASDisplayNodeCAssert([sortedIndexPaths isEqualToArray:indexPaths], @"Expected array of index paths to be sorted in descending order."); +#endif + + /** + * It is tempting to do something clever here and collect indexes into ranges or NSIndexSets + * but deep down, __NSArrayM only implements removeObjectAtIndex: and so doing all that extra + * work ends up running the same code. + */ + for (NSIndexPath *indexPath in indexPaths) { + NSInteger section = indexPath.section; + if (section >= mutableArray.count) { + ASDisplayNodeCFailAssert(@"Invalid section index %zd – only %zd sections", section, mutableArray.count); + continue; + } + + NSMutableArray *subarray = mutableArray[section]; + NSInteger item = indexPath.item; + if (item >= subarray.count) { + ASDisplayNodeCFailAssert(@"Invalid item index %zd – only %zd items in section %zd", item, subarray.count, section); + continue; + } + [subarray removeObjectAtIndex:item]; + } +} + +NSArray *ASIndexPathsForTwoDimensionalArray(NSArray * twoDimensionalArray) +{ + NSMutableArray *result = [NSMutableArray array]; + NSInteger section = 0; + NSInteger i = 0; + for (NSArray *subarray in twoDimensionalArray) { + ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); + NSInteger itemCount = subarray.count; + for (NSInteger item = 0; item < itemCount; item++) { + result[i++] = [NSIndexPath indexPathForItem:item inSection:section]; + } + section++; + } + return result; +} + +NSArray *ASElementsInTwoDimensionalArray(NSArray * twoDimensionalArray) +{ + NSMutableArray *result = [NSMutableArray array]; + NSInteger i = 0; + for (NSArray *subarray in twoDimensionalArray) { + ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); + for (id element in subarray) { + result[i++] = element; + } + } + return result; +} + +id ASGetElementInTwoDimensionalArray(NSArray *array, NSIndexPath *indexPath) +{ + ASDisplayNodeCAssertNotNil(indexPath, @"Expected non-nil index path"); + ASDisplayNodeCAssert(indexPath.length == 2, @"Expected index path of length 2. Index path: %@", indexPath); + NSInteger section = indexPath.section; + if (array.count <= section) { + return nil; + } + + NSArray *innerArray = array[section]; + NSInteger item = indexPath.item; + if (innerArray.count <= item) { + return nil; + } + return innerArray[item]; +} diff --git a/AsyncDisplayKit/Private/ASWeakMap.h b/Source/Private/ASWeakMap.h similarity index 97% rename from AsyncDisplayKit/Private/ASWeakMap.h rename to Source/Private/ASWeakMap.h index 5610b4d580..cbe75c9747 100644 --- a/AsyncDisplayKit/Private/ASWeakMap.h +++ b/Source/Private/ASWeakMap.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN * This class is used in conjunction with ASWeakMap. Instances of this type are returned by an ASWeakMap, * must retain this value for as long as they want the entry to exist in the map. */ +AS_SUBCLASSING_RESTRICTED @interface ASWeakMapEntry : NSObject @property (nonatomic, retain, readonly) Value value; @@ -42,6 +43,7 @@ NS_ASSUME_NONNULL_BEGIN * * The underlying storage is a hash table and the Key type should implement `hash` and `isEqual:`. */ +AS_SUBCLASSING_RESTRICTED @interface ASWeakMap<__covariant Key : NSObject *, Value> : NSObject /** diff --git a/AsyncDisplayKit/Private/ASWeakMap.m b/Source/Private/ASWeakMap.m similarity index 98% rename from AsyncDisplayKit/Private/ASWeakMap.m rename to Source/Private/ASWeakMap.m index 1c9f6896c9..16b7d688d7 100644 --- a/AsyncDisplayKit/Private/ASWeakMap.m +++ b/Source/Private/ASWeakMap.m @@ -10,7 +10,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASWeakMap.h" +#import @interface ASWeakMapEntry () @property (nonatomic, strong) NSObject *key; diff --git a/AsyncDisplayKit/Layout/ASLayoutElementStylePrivate.h b/Source/Private/Layout/ASLayoutElementStylePrivate.h similarity index 94% rename from AsyncDisplayKit/Layout/ASLayoutElementStylePrivate.h rename to Source/Private/Layout/ASLayoutElementStylePrivate.h index 52347fe862..34a4852ca7 100644 --- a/AsyncDisplayKit/Layout/ASLayoutElementStylePrivate.h +++ b/Source/Private/Layout/ASLayoutElementStylePrivate.h @@ -10,7 +10,7 @@ #pragma once -#import "ASObjectDescriptionHelpers.h" +#import @interface ASLayoutElementStyle () diff --git a/AsyncDisplayKit/Private/ASLayoutSpecPrivate.h b/Source/Private/Layout/ASLayoutSpecPrivate.h similarity index 84% rename from AsyncDisplayKit/Private/ASLayoutSpecPrivate.h rename to Source/Private/Layout/ASLayoutSpecPrivate.h index 8a606483f1..0c31ec6cc9 100644 --- a/AsyncDisplayKit/Private/ASLayoutSpecPrivate.h +++ b/Source/Private/Layout/ASLayoutSpecPrivate.h @@ -10,16 +10,14 @@ // of patent rights can be found in the PATENTS file in the same directory. // - -#import "ASInternalHelpers.h" -#import "ASEnvironmentInternal.h" -#import "ASThread.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @interface ASLayoutSpec() { ASDN::RecursiveMutex __instanceLock__; - ASEnvironmentState _environmentState; + ASPrimitiveTraitCollection _primitiveTraitCollection; ASLayoutElementStyle *_style; NSMutableArray *_childrenArray; } diff --git a/AsyncDisplayKit/Private/ASLayoutSpecUtilities.h b/Source/Private/Layout/ASLayoutSpecUtilities.h similarity index 98% rename from AsyncDisplayKit/Private/ASLayoutSpecUtilities.h rename to Source/Private/Layout/ASLayoutSpecUtilities.h index 384327060f..3326280343 100644 --- a/AsyncDisplayKit/Private/ASLayoutSpecUtilities.h +++ b/Source/Private/Layout/ASLayoutSpecUtilities.h @@ -8,13 +8,13 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import + #import #import #import #import -#import - namespace AS { // adopted from http://stackoverflow.com/questions/14945223/map-function-with-c11-constructs // Takes an iterable, applies a function to every element, @@ -102,4 +102,3 @@ inline UIEdgeInsets operator-(const UIEdgeInsets &e) { return { -e.top, -e.left, -e.bottom, -e.right }; } - diff --git a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h b/Source/Private/Layout/ASStackLayoutSpecUtilities.h similarity index 93% rename from AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h rename to Source/Private/Layout/ASStackLayoutSpecUtilities.h index a5067a631e..fbcafdff41 100644 --- a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h +++ b/Source/Private/Layout/ASStackLayoutSpecUtilities.h @@ -8,14 +8,15 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASStackLayoutSpec.h" +#import typedef struct { ASStackLayoutDirection direction; CGFloat spacing; ASStackLayoutJustifyContent justifyContent; ASStackLayoutAlignItems alignItems; - BOOL baselineRelativeArrangement; + ASStackLayoutFlexWrap flexWrap; + ASStackLayoutAlignContent alignContent; } ASStackLayoutSpecStyle; inline CGFloat stackDimension(const ASStackLayoutDirection direction, const CGSize size) @@ -43,6 +44,10 @@ inline CGSize directionSize(const ASStackLayoutDirection direction, const CGFloa return (direction == ASStackLayoutDirectionVertical) ? CGSizeMake(cross, stack) : CGSizeMake(stack, cross); } +inline void setStackValueToPoint(const ASStackLayoutDirection direction, const CGFloat stack, CGPoint &point) { + (direction == ASStackLayoutDirectionVertical) ? (point.y = stack) : (point.x = stack); +} + inline ASSizeRange directionSizeRange(const ASStackLayoutDirection direction, const CGFloat stackMin, const CGFloat stackMax, diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.h b/Source/Private/Layout/ASStackPositionedLayout.h similarity index 82% rename from AsyncDisplayKit/Private/ASStackPositionedLayout.h rename to Source/Private/Layout/ASStackPositionedLayout.h index a879763297..dd0b697555 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.h +++ b/Source/Private/Layout/ASStackPositionedLayout.h @@ -8,15 +8,16 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASLayout.h" -#import "ASDimension.h" -#import "ASStackUnpositionedLayout.h" +#import +#import +#import /** Represents a set of laid out and positioned stack layout children. */ struct ASStackPositionedLayout { const std::vector items; - const CGFloat crossSize; - + /** Final size of the stack */ + const CGSize size; + /** Given an unpositioned layout, computes the positions each child should be placed at. */ static ASStackPositionedLayout compute(const ASStackUnpositionedLayout &unpositionedLayout, const ASStackLayoutSpecStyle &style, diff --git a/Source/Private/Layout/ASStackPositionedLayout.mm b/Source/Private/Layout/ASStackPositionedLayout.mm new file mode 100644 index 0000000000..c39bdcdab6 --- /dev/null +++ b/Source/Private/Layout/ASStackPositionedLayout.mm @@ -0,0 +1,186 @@ +// +// ASStackPositionedLayout.mm +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import +#import + +#import +#import +#import +#import + +static CGFloat crossOffsetForItem(const ASStackLayoutSpecItem &item, + const ASStackLayoutSpecStyle &style, + const CGFloat crossSize, + const CGFloat baseline) +{ + switch (alignment(item.child.style.alignSelf, style.alignItems)) { + case ASStackLayoutAlignItemsEnd: + return crossSize - crossDimension(style.direction, item.layout.size); + case ASStackLayoutAlignItemsCenter: + return ASFloorPixelValue((crossSize - crossDimension(style.direction, item.layout.size)) / 2); + case ASStackLayoutAlignItemsBaselineFirst: + case ASStackLayoutAlignItemsBaselineLast: + return baseline - ASStackUnpositionedLayout::baselineForItem(style, item); + case ASStackLayoutAlignItemsStart: + case ASStackLayoutAlignItemsStretch: + case ASStackLayoutAlignItemsNotSet: + return 0; + } +} + +static void crossOffsetAndSpacingForEachLine(const std::size_t numOfLines, + const CGFloat crossViolation, + ASStackLayoutAlignContent alignContent, + CGFloat &offset, + CGFloat &spacing) +{ + ASDisplayNodeCAssertTrue(numOfLines > 0); + + // Handle edge cases + if (alignContent == ASStackLayoutAlignContentSpaceBetween && (crossViolation < kViolationEpsilon || numOfLines == 1)) { + alignContent = ASStackLayoutAlignContentStart; + } else if (alignContent == ASStackLayoutAlignContentSpaceAround && (crossViolation < kViolationEpsilon || numOfLines == 1)) { + alignContent = ASStackLayoutAlignContentCenter; + } + + offset = 0; + spacing = 0; + + switch (alignContent) { + case ASStackLayoutAlignContentCenter: + offset = crossViolation / 2; + break; + case ASStackLayoutAlignContentEnd: + offset = crossViolation; + break; + case ASStackLayoutAlignContentSpaceBetween: + // Spacing between the items, no spaces at the edges, evenly distributed + spacing = crossViolation / (numOfLines - 1); + break; + case ASStackLayoutAlignContentSpaceAround: { + // Spacing between items are twice the spacing on the edges + CGFloat spacingUnit = crossViolation / (numOfLines * 2); + offset = spacingUnit; + spacing = spacingUnit * 2; + break; + } + case ASStackLayoutAlignContentStart: + case ASStackLayoutAlignContentStretch: + break; + } +} + +static void stackOffsetAndSpacingForEachItem(const std::size_t numOfItems, + const CGFloat stackViolation, + ASStackLayoutJustifyContent justifyContent, + CGFloat &offset, + CGFloat &spacing) +{ + ASDisplayNodeCAssertTrue(numOfItems > 0); + + // Handle edge cases + if (justifyContent == ASStackLayoutJustifyContentSpaceBetween && (stackViolation < kViolationEpsilon || numOfItems == 1)) { + justifyContent = ASStackLayoutJustifyContentStart; + } else if (justifyContent == ASStackLayoutJustifyContentSpaceAround && (stackViolation < kViolationEpsilon || numOfItems == 1)) { + justifyContent = ASStackLayoutJustifyContentCenter; + } + + offset = 0; + spacing = 0; + + switch (justifyContent) { + case ASStackLayoutJustifyContentCenter: + offset = stackViolation / 2; + break; + case ASStackLayoutJustifyContentEnd: + offset = stackViolation; + break; + case ASStackLayoutJustifyContentSpaceBetween: + // Spacing between the items, no spaces at the edges, evenly distributed + spacing = stackViolation / (numOfItems - 1); + break; + case ASStackLayoutJustifyContentSpaceAround: { + // Spacing between items are twice the spacing on the edges + CGFloat spacingUnit = stackViolation / (numOfItems * 2); + offset = spacingUnit; + spacing = spacingUnit * 2; + break; + } + case ASStackLayoutJustifyContentStart: + break; + } +} + +static void positionItemsInLine(const ASStackUnpositionedLine &line, + const ASStackLayoutSpecStyle &style, + const CGPoint &startingPoint, + const CGFloat stackSpacing) +{ + CGPoint p = startingPoint; + BOOL first = YES; + + for (const auto &item : line.items) { + p = p + directionPoint(style.direction, item.child.style.spacingBefore, 0); + if (!first) { + p = p + directionPoint(style.direction, style.spacing + stackSpacing, 0); + } + first = NO; + item.layout.position = p + directionPoint(style.direction, 0, crossOffsetForItem(item, style, line.crossSize, line.baseline)); + + p = p + directionPoint(style.direction, stackDimension(style.direction, item.layout.size) + item.child.style.spacingAfter, 0); + } +} + +ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &layout, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange) +{ + const auto &lines = layout.lines; + if (lines.empty()) { + return {}; + } + + const auto numOfLines = lines.size(); + const auto direction = style.direction; + const auto alignContent = style.alignContent; + const auto justifyContent = style.justifyContent; + const auto crossViolation = ASStackUnpositionedLayout::computeCrossViolation(layout.crossDimensionSum, style, sizeRange); + CGFloat crossOffset; + CGFloat crossSpacing; + crossOffsetAndSpacingForEachLine(numOfLines, crossViolation, alignContent, crossOffset, crossSpacing); + + std::vector positionedItems; + CGPoint p = directionPoint(direction, 0, crossOffset); + BOOL first = YES; + for (const auto &line : lines) { + if (!first) { + p = p + directionPoint(direction, 0, crossSpacing); + } + first = NO; + + const auto &items = line.items; + const auto stackViolation = ASStackUnpositionedLayout::computeStackViolation(line.stackDimensionSum, style, sizeRange); + CGFloat stackOffset; + CGFloat stackSpacing; + stackOffsetAndSpacingForEachItem(items.size(), stackViolation, justifyContent, stackOffset, stackSpacing); + + setStackValueToPoint(direction, stackOffset, p); + positionItemsInLine(line, style, p, stackSpacing); + std::move(items.begin(), items.end(), std::back_inserter(positionedItems)); + + p = p + directionPoint(direction, -stackOffset, line.crossSize); + } + + const CGSize finalSize = directionSize(direction, layout.stackDimensionSum, layout.crossDimensionSum); + return {std::move(positionedItems), ASSizeRangeClamp(sizeRange, finalSize)}; +} diff --git a/Source/Private/Layout/ASStackUnpositionedLayout.h b/Source/Private/Layout/ASStackUnpositionedLayout.h new file mode 100644 index 0000000000..88781b14c1 --- /dev/null +++ b/Source/Private/Layout/ASStackUnpositionedLayout.h @@ -0,0 +1,74 @@ +// +// ASStackUnpositionedLayout.h +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import +#import +#import + +/** The threshold that determines if a violation has actually occurred. */ +extern CGFloat const kViolationEpsilon; + +struct ASStackLayoutSpecChild { + /** The original source child. */ + id element; + /** Style object of element. */ + ASLayoutElementStyle *style; + /** Size object of the element */ + ASLayoutElementSize size; +}; + +struct ASStackLayoutSpecItem { + /** The original source child. */ + ASStackLayoutSpecChild child; + /** The proposed layout or nil if no is calculated yet. */ + ASLayout *layout; +}; + +struct ASStackUnpositionedLine { + /** The set of proposed children in this line, each contains child layout, not yet positioned. */ + std::vector items; + /** The total size of the children in the stack dimension, including all spacing. */ + CGFloat stackDimensionSum; + /** The size in the cross dimension */ + CGFloat crossSize; + /** The baseline of the stack which baseline aligned children should align to */ + CGFloat baseline; +}; + +/** Represents a set of stack layout children that have their final layout computed, but are not yet positioned. */ +struct ASStackUnpositionedLayout { + /** The set of proposed lines, each contains child layouts, not yet positioned. */ + const std::vector lines; + /** + * In a single line stack (e.g no wrao), this is the total size of the children in the stack dimension, including all spacing. + * In a multi-line stack, this is the largest stack dimension among lines. + */ + const CGFloat stackDimensionSum; + const CGFloat crossDimensionSum; + + /** Given a set of children, computes the unpositioned layouts for those children. */ + static ASStackUnpositionedLayout compute(const std::vector &children, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange, + const BOOL concurrent); + + static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style, + const ASStackLayoutSpecItem &l); + + static CGFloat computeStackViolation(const CGFloat stackDimensionSum, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange); + + static CGFloat computeCrossViolation(const CGFloat crossDimensionSum, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange); +}; diff --git a/Source/Private/Layout/ASStackUnpositionedLayout.mm b/Source/Private/Layout/ASStackUnpositionedLayout.mm new file mode 100644 index 0000000000..3ab2c09a4f --- /dev/null +++ b/Source/Private/Layout/ASStackUnpositionedLayout.mm @@ -0,0 +1,751 @@ +// +// ASStackUnpositionedLayout.mm +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import +#import + +#import +#import +#import + +CGFloat const kViolationEpsilon = 0.01; + +static CGFloat resolveCrossDimensionMaxForStretchChild(const ASStackLayoutSpecStyle &style, + const ASStackLayoutSpecChild &child, + const CGFloat stackMax, + const CGFloat crossMax) +{ + // stretched children may have a cross direction max that is smaller than the minimum size constraint of the parent. + const CGFloat computedMax = (style.direction == ASStackLayoutDirectionVertical ? + ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.width : + ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.height); + return computedMax == INFINITY ? crossMax : computedMax; +} + +static CGFloat resolveCrossDimensionMinForStretchChild(const ASStackLayoutSpecStyle &style, + const ASStackLayoutSpecChild &child, + const CGFloat stackMax, + const CGFloat crossMin) +{ + // stretched children will have a cross dimension of at least crossMin, unless they explicitly define a child size + // that is smaller than the constraint of the parent. + return (style.direction == ASStackLayoutDirectionVertical ? + ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).min.width : + ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).min.height) ?: crossMin; +} + +/** + Sizes the child given the parameters specified, and returns the computed layout. + */ +static ASLayout *crossChildLayout(const ASStackLayoutSpecChild &child, + const ASStackLayoutSpecStyle &style, + const CGFloat stackMin, + const CGFloat stackMax, + const CGFloat crossMin, + const CGFloat crossMax, + const CGSize parentSize) +{ + const ASStackLayoutAlignItems alignItems = alignment(child.style.alignSelf, style.alignItems); + // stretched children will have a cross dimension of at least crossMin + const CGFloat childCrossMin = (alignItems == ASStackLayoutAlignItemsStretch ? + resolveCrossDimensionMinForStretchChild(style, child, stackMax, crossMin) : + 0); + const CGFloat childCrossMax = (alignItems == ASStackLayoutAlignItemsStretch ? + resolveCrossDimensionMaxForStretchChild(style, child, stackMax, crossMax) : + crossMax); + const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, childCrossMax); + ASLayout *layout = [child.element layoutThatFits:childSizeRange parentSize:parentSize]; + ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from measureWithSizeRange: must not be nil: %@", child.element); + return layout ? : [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]; +} + +static void dispatchApplyIfNeeded(size_t iterationCount, BOOL forced, void(^work)(size_t i)) +{ + if (iterationCount == 0) { + return; + } + + if (iterationCount == 1) { + work(0); + return; + } + + // TODO Once the locking situation in ASDisplayNode has improved, always dispatch if on main + if (forced == NO) { + for (size_t i = 0; i < iterationCount; i++) { + work(i); + } + return; + } + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + ASDispatchApply(iterationCount, queue, 0, work); +} + +/** + Computes the consumed cross dimension length for the given vector of lines and stacking style. + + Cross Dimension + +---------------------> + +--------+ +--------+ +--------+ +---------+ + Vertical |Vertical| |Vertical| |Vertical| |Vertical | + Stack | Line 1 | | Line 2 | | Line 3 | | Line 4 | + | | | | | | | | + +--------+ +--------+ +--------+ +---------+ + crossDimensionSum + |------------------------------------------| + + @param lines unpositioned lines + */ +static CGFloat computeLinesCrossDimensionSum(const std::vector &lines) +{ + return std::accumulate(lines.begin(), lines.end(), 0.0, + [&](CGFloat x, const ASStackUnpositionedLine &l) { + return x + l.crossSize; + }); +} + + +/** + Computes the violation by comparing a cross dimension sum with the overall allowable size range for the stack. + + Violation is the distance you would have to add to the unbounded cross-direction length of the stack spec's + lines in order to bring the stack within its allowed sizeRange. The diagram below shows 3 vertical stacks, each contains 3-5 vertical lines, + with the different types of violation. + + Cross Dimension + +---------------------> + cross size range + |------------| + +--------+ +--------+ +--------+ +---------+ - - - - - - - - + Vertical |Vertical| |Vertical| |Vertical| |Vertical | | ^ + Stack 1 | Line 1 | | Line 2 | | Line 3 | | Line 4 | (zero violation) | stack size range + | | | | | | | | | | v + +--------+ +--------+ +--------+ +---------+ - - - - - - - - + | | + +--------+ +--------+ +--------+ - - - - - - - - - - - - + Vertical | | | | | | | | ^ + Stack 2 | | | | | |<--> (positive violation) | stack size range + | | | | | | | | v + +--------+ +--------+ +--------+ - - - - - - - - - - - - + | |<------> (negative violation) + +--------+ +--------+ +--------+ +---------+ +-----------+ - - - + Vertical | | | | | | | | | | | | ^ + Stack 3 | | | | | | | | | | | stack size range + | | | | | | | | | | | | v + +--------+ +--------+ +--------+ +---------+ +-----------+ - - - + + @param crossDimensionSum the consumed length of the lines in the stack along the cross dimension + @param style layout style to be applied to all children + @param sizeRange the range of allowable sizes for the stack layout spec + */ +CGFloat ASStackUnpositionedLayout::computeCrossViolation(const CGFloat crossDimensionSum, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange) +{ + const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); + const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); + if (crossDimensionSum < minCrossDimension) { + return minCrossDimension - crossDimensionSum; + } else if (crossDimensionSum > maxCrossDimension) { + return maxCrossDimension - crossDimensionSum; + } + return 0; +} + +/** + Stretches children to lay out along the cross axis according to the alignment stretch settings of the children + (child.alignSelf), and the stack layout's alignment settings (style.alignItems). This does not do the actual alignment + of the items once stretched though; ASStackPositionedLayout will do centering etc. + + Finds the maximum cross dimension among child layouts. If that dimension exceeds the minimum cross layout size then + we must stretch any children whose alignItems specify ASStackLayoutAlignItemsStretch. + + The diagram below shows 3 children in a horizontal stack. The second child is larger than the minCrossDimension, so + its height is used as the childCrossMax. Any children that are stretchable (which may be all children if + style.alignItems specifies stretch) like the first child must be stretched to match that maximum. All children must be + at least minCrossDimension in cross dimension size, which is shown by the sizing of the third child. + + Stack Dimension + +---------------------> + + +-+-------------+-+-------------+--+---------------+ + + + + | | child. | | | | | | | | + | | alignSelf | | | | | | | | + Cross | | = stretch | | | +-------+-------+ | | | + Dimension | +-----+-------+ | | | | | | | | + | | | | | | | | | | + | | | | | v | | | | + v +-+- - - - - - -+-+ - - - - - - +--+- - - - - - - -+ | | + minCrossDimension + | | | | | + | v | | | | | + +- - - - - - -+ +-------------+ | + childCrossMax + | + +--------------------------------------------------+ + crossMax + + @param items pre-computed items; modified in-place as needed + @param style the layout style of the overall stack layout + */ +static void stretchItemsAlongCrossDimension(std::vector &items, + const ASStackLayoutSpecStyle &style, + const BOOL concurrent, + const CGSize parentSize, + const CGFloat crossSize) +{ + dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { + auto &item = items[i]; + const ASStackLayoutAlignItems alignItems = alignment(item.child.style.alignSelf, style.alignItems); + if (alignItems == ASStackLayoutAlignItemsStretch) { + const CGFloat cross = crossDimension(style.direction, item.layout.size); + const CGFloat stack = stackDimension(style.direction, item.layout.size); + const CGFloat violation = crossSize - cross; + + // Only stretch if violation is positive. Compare against kViolationEpsilon here to avoid stretching against a tiny violation. + if (violation > kViolationEpsilon) { + item.layout = crossChildLayout(item.child, style, stack, stack, crossSize, crossSize, parentSize); + } + } + }); +} + +/** + * Stretch lines and their items according to alignContent, alignItems and alignSelf. + * https://www.w3.org/TR/css-flexbox-1/#algo-line-stretch + * https://www.w3.org/TR/css-flexbox-1/#algo-stretch + */ +static void stretchLinesAlongCrossDimension(std::vector &lines, + const ASStackLayoutSpecStyle &style, + const BOOL concurrent, + const ASSizeRange &sizeRange, + const CGSize parentSize) +{ + ASDisplayNodeCAssertFalse(lines.empty()); + const std::size_t numOfLines = lines.size(); + const CGFloat violation = ASStackUnpositionedLayout::computeCrossViolation(computeLinesCrossDimensionSum(lines), style, sizeRange); + // Don't stretch if the stack is single line, because the line's cross size was clamped against the stack's constrained size. + const BOOL shouldStretchLines = (numOfLines > 1 + && style.alignContent == ASStackLayoutAlignContentStretch + && violation > kViolationEpsilon); + + CGFloat extraCrossSizePerLine = violation / numOfLines; + for (auto &line : lines) { + if (shouldStretchLines) { + line.crossSize += extraCrossSizePerLine; + } + + stretchItemsAlongCrossDimension(line.items, style, concurrent, parentSize, line.crossSize); + } +} + +static BOOL itemIsBaselineAligned(const ASStackLayoutSpecStyle &style, + const ASStackLayoutSpecItem &l) +{ + ASStackLayoutAlignItems alignItems = alignment(l.child.style.alignSelf, style.alignItems); + return alignItems == ASStackLayoutAlignItemsBaselineFirst || alignItems == ASStackLayoutAlignItemsBaselineLast; +} + +CGFloat ASStackUnpositionedLayout::baselineForItem(const ASStackLayoutSpecStyle &style, + const ASStackLayoutSpecItem &item) +{ + switch (alignment(item.child.style.alignSelf, style.alignItems)) { + case ASStackLayoutAlignItemsBaselineFirst: + return item.child.style.ascender; + case ASStackLayoutAlignItemsBaselineLast: + return crossDimension(style.direction, item.layout.size) + item.child.style.descender; + default: + return 0; + } +} + +/** + * Computes cross size and baseline of each line. + * https://www.w3.org/TR/css-flexbox-1/#algo-cross-line + * + * @param lines All items to lay out + * @param style the layout style of the overall stack layout + * @param sizeRange the range of allowable sizes for the stack layout component + */ +static void computeLinesCrossSizeAndBaseline(std::vector &lines, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange) +{ + ASDisplayNodeCAssertFalse(lines.empty()); + const BOOL isSingleLine = (lines.size() == 1); + + const auto minCrossSize = crossDimension(style.direction, sizeRange.min); + const auto maxCrossSize = crossDimension(style.direction, sizeRange.max); + const BOOL definiteCrossSize = (minCrossSize == maxCrossSize); + + // If the stack is single-line and has a definite cross size, the cross size of the line is the stack's definite cross size. + if (isSingleLine && definiteCrossSize) { + auto &line = lines[0]; + line.crossSize = minCrossSize; + + // We still need to determine the line's baseline + //TODO unit test + for (const auto &item : line.items) { + if (itemIsBaselineAligned(style, item)) { + CGFloat baseline = ASStackUnpositionedLayout::baselineForItem(style, item); + line.baseline = MAX(line.baseline, baseline); + } + } + + return; + } + + for (auto &line : lines) { + const auto &items = line.items; + CGFloat maxStartToBaselineDistance = 0; + CGFloat maxBaselineToEndDistance = 0; + CGFloat maxItemCrossSize = 0; + + for (const auto &item : items) { + if (itemIsBaselineAligned(style, item)) { + // Step 1. Collect all the items whose align-self is baseline. Find the largest of the distances + // between each item’s baseline and its hypothetical outer cross-start edge (aka. its baseline value), + // and the largest of the distances between each item’s baseline and its hypothetical outer cross-end edge, + // and sum these two values. + CGFloat baseline = ASStackUnpositionedLayout::baselineForItem(style, item); + maxStartToBaselineDistance = MAX(maxStartToBaselineDistance, baseline); + maxBaselineToEndDistance = MAX(maxBaselineToEndDistance, crossDimension(style.direction, item.layout.size) - baseline); + } else { + // Step 2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size. + maxItemCrossSize = MAX(maxItemCrossSize, crossDimension(style.direction, item.layout.size)); + } + } + + // Step 3. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero. + line.crossSize = MAX(maxStartToBaselineDistance + maxBaselineToEndDistance, maxItemCrossSize); + if (isSingleLine) { + // If the stack is single-line, then clamp the line’s cross-size to be within the stack's min and max cross-size properties. + line.crossSize = MIN(MAX(minCrossSize, line.crossSize), maxCrossSize); + } + + line.baseline = maxStartToBaselineDistance; + } +} + +/** + Returns a lambda that computes the relevant flex factor based on the given violation. + @param violation The amount that the stack layout violates its size range. See header for sign interpretation. + */ +static std::function flexFactorInViolationDirection(const CGFloat violation) +{ + if (std::fabs(violation) < kViolationEpsilon) { + return [](const ASStackLayoutSpecItem &item) { return 0.0; }; + } else if (violation > 0) { + return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexGrow; }; + } else { + return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexShrink; }; + } +} + +static inline CGFloat scaledFlexShrinkFactor(const ASStackLayoutSpecItem &item, + const ASStackLayoutSpecStyle &style, + const CGFloat flexFactorSum) +{ + return stackDimension(style.direction, item.layout.size) * (item.child.style.flexShrink / flexFactorSum); +} + +/** + Returns a lambda that computes a flex shrink adjustment for a given item based on the provided violation. + @param items The unpositioned items from the original unconstrained layout pass. + @param style The layout style to be applied to all children. + @param violation The amount that the stack layout violates its size range. + @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. + @return A lambda capable of computing the flex shrink adjustment, if any, for a particular item. + */ +static std::function flexShrinkAdjustment(const std::vector &items, + const ASStackLayoutSpecStyle &style, + const CGFloat violation, + const CGFloat flexFactorSum) +{ + const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) { + return x + scaledFlexShrinkFactor(item, style, flexFactorSum); + }); + return [style, scaledFlexShrinkFactorSum, violation, flexFactorSum](const ASStackLayoutSpecItem &item) { + if (scaledFlexShrinkFactorSum == 0.0) { + return (CGFloat)0.0; + } + + const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style, flexFactorSum) / scaledFlexShrinkFactorSum; + // The item should shrink proportionally to the scaled flex shrink factor ratio computed above. + // Unlike the flex grow adjustment the flex shrink adjustment needs to take the size of each item into account. + return -std::fabs(scaledFlexShrinkFactorRatio * violation); + }; +} + +/** + Returns a lambda that computes a flex grow adjustment for a given item based on the provided violation. + @param items The unpositioned items from the original unconstrained layout pass. + @param violation The amount that the stack layout violates its size range. + @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. + @return A lambda capable of computing the flex grow adjustment, if any, for a particular item. + */ +static std::function flexGrowAdjustment(const std::vector &items, + const CGFloat violation, + const CGFloat flexFactorSum) +{ + // To compute the flex grow adjustment distribute the violation proportionally based on each item's flex grow factor. + return [violation, flexFactorSum](const ASStackLayoutSpecItem &item) { + return std::floor(violation * (item.child.style.flexGrow / flexFactorSum)); + }; +} + +/** + Returns a lambda that computes a flex adjustment for a given item based on the provided violation. + @param items The unpositioned items from the original unconstrained layout pass. + @param style The layout style to be applied to all children. + @param violation The amount that the stack layout violates its size range. + @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. + @return A lambda capable of computing the flex adjustment for a particular item. + */ +static std::function flexAdjustmentInViolationDirection(const std::vector &items, + const ASStackLayoutSpecStyle &style, + const CGFloat violation, + const CGFloat flexFactorSum) +{ + if (violation > 0) { + return flexGrowAdjustment(items, violation, flexFactorSum); + } else { + return flexShrinkAdjustment(items, style, violation, flexFactorSum); + } +} + +ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(const ASStackLayoutSpecChild &child) +{ + return child.style.flexGrow > 0 && child.style.flexShrink > 0; +} + +/** + The flexible children may have been left not laid out in the initial layout pass, so we may have to go through and size + these children at zero size so that the children layouts are at least present. + */ +static void layoutFlexibleChildrenAtZeroSize(std::vector &items, + const ASStackLayoutSpecStyle &style, + const BOOL concurrent, + const ASSizeRange &sizeRange, + const CGSize parentSize) +{ + dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { + auto &item = items[i]; + if (isFlexibleInBothDirections(item.child)) { + item.layout = crossChildLayout(item.child, + style, + 0, + 0, + crossDimension(style.direction, sizeRange.min), + crossDimension(style.direction, sizeRange.max), + parentSize); + } + }); +} + +/** + Computes the consumed stack dimension length for the given vector of items and stacking style. + + stackDimensionSum + <-----------------------> + +-----+ +-------+ +---+ + | | | | | | + | | | | | | + +-----+ | | +---+ + +-------+ + + @param items unpositioned layouts for items + @param style the layout style of the overall stack layout + */ +static CGFloat computeItemsStackDimensionSum(const std::vector &items, + const ASStackLayoutSpecStyle &style) +{ + // Sum up the childrens' spacing + const CGFloat childSpacingSum = std::accumulate(items.begin(), items.end(), + // Start from default spacing between each child: + items.empty() ? 0 : style.spacing * (items.size() - 1), + [&](CGFloat x, const ASStackLayoutSpecItem &l) { + return x + l.child.style.spacingBefore + l.child.style.spacingAfter; + }); + + // Sum up the childrens' dimensions (including spacing) in the stack direction. + const CGFloat childStackDimensionSum = std::accumulate(items.begin(), items.end(), childSpacingSum, + [&](CGFloat x, const ASStackLayoutSpecItem &l) { + return x + stackDimension(style.direction, l.layout.size); + }); + return childStackDimensionSum; +} + +//TODO move this up near computeCrossViolation and make both methods share the same code path, to make sure they share the same concept of "negative" and "positive" violations. +/** + Computes the violation by comparing a stack dimension sum with the overall allowable size range for the stack. + + Violation is the distance you would have to add to the unbounded stack-direction length of the stack spec's + children in order to bring the stack within its allowed sizeRange. The diagram below shows 3 horizontal stacks with + the different types of violation. + + sizeRange + |------------| + +------+ +-------+ +-------+ +---------+ + | | | | | | | | | | + | | | | | | | | (zero violation) + | | | | | | | | | | + +------+ +-------+ +-------+ +---------+ + | | + +------+ +-------+ +-------+ + | | | | | | | | + | | | | | |<--> (positive violation) + | | | | | | | | + +------+ +-------+ +-------+ + | |<------> (negative violation) + +------+ +-------+ +-------+ +---------+ +-----------+ + | | | | | | | | | | | | + | | | | | | | | | | + | | | | | | | | | | | | + +------+ +-------+ +-------+ +---------+ +-----------+ + + @param stackDimensionSum the consumed length of the children in the stack along the stack dimension + @param style layout style to be applied to all children + @param sizeRange the range of allowable sizes for the stack layout spec + */ +CGFloat ASStackUnpositionedLayout::computeStackViolation(const CGFloat stackDimensionSum, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange) +{ + const CGFloat minStackDimension = stackDimension(style.direction, sizeRange.min); + const CGFloat maxStackDimension = stackDimension(style.direction, sizeRange.max); + if (stackDimensionSum < minStackDimension) { + return minStackDimension - stackDimensionSum; + } else if (stackDimensionSum > maxStackDimension) { + return maxStackDimension - stackDimensionSum; + } + return 0; +} + +/** + If we have a single flexible (both shrinkable and growable) child, and our allowed size range is set to a specific + number then we may avoid the first "intrinsic" size calculation. + */ +ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector &children, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange) +{ + const NSUInteger flexibleChildren = std::count_if(children.begin(), children.end(), isFlexibleInBothDirections); + return ((flexibleChildren == 1) + && (stackDimension(style.direction, sizeRange.min) == + stackDimension(style.direction, sizeRange.max))); +} + +/** + Flexes children in the stack axis to resolve a min or max stack size violation. First, determines which children are + flexible (see computeStackViolation and isFlexibleInViolationDirection). Then computes how much to flex each flexible child + and performs re-layout. Note that there may still be a non-zero violation even after flexing. + + The actual CSS flexbox spec describes an iterative looping algorithm here, which may be adopted in t5837937: + http://www.w3.org/TR/css3-flexbox/#resolve-flexible-lengths + + @param lines reference to unpositioned lines and items from the original, unconstrained layout pass; modified in-place + @param style layout style to be applied to all children + @param sizeRange the range of allowable sizes for the stack layout component + @param parentSize Size of the stack layout component. May be undefined in either or both directions. + */ +static void flexLinesAlongStackDimension(std::vector &lines, + const ASStackLayoutSpecStyle &style, + const BOOL concurrent, + const ASSizeRange &sizeRange, + const CGSize parentSize, + const BOOL useOptimizedFlexing) +{ + for (auto &line : lines) { + auto &items = line.items; + const CGFloat violation = ASStackUnpositionedLayout::computeStackViolation(computeItemsStackDimensionSum(items, style), style, sizeRange); + std::function flexFactor = flexFactorInViolationDirection(violation); + // The flex factor sum is needed to determine if flexing is necessary. + // This value is also needed if the violation is positive and flexible items need to grow, so keep it around. + const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) { + return x + flexFactor(item); + }); + + // If no items are able to flex then there is nothing left to do with this line. Bail. + if (flexFactorSum == 0) { + // If optimized flexing was used then we have to clean up the unsized items and lay them out at zero size. + if (useOptimizedFlexing) { + layoutFlexibleChildrenAtZeroSize(items, style, concurrent, sizeRange, parentSize); + } + continue; + } + + std::function flexAdjustment = flexAdjustmentInViolationDirection(items, + style, + violation, + flexFactorSum); + // Compute any remaining violation to the first flexible item. + const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackLayoutSpecItem &item) { + return x - flexAdjustment(item); + }); + + size_t firstFlexItem = -1; + for(size_t i = 0; i < items.size(); i++) { + // Items are consider inflexible if they do not need to make a flex adjustment. + if (flexAdjustment(items[i]) != 0) { + firstFlexItem = i; + break; + } + } + if (firstFlexItem == -1) { + continue; + } + + dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { + auto &item = items[i]; + const CGFloat currentFlexAdjustment = flexAdjustment(item); + // Items are consider inflexible if they do not need to make a flex adjustment. + if (currentFlexAdjustment != 0) { + const CGFloat originalStackSize = stackDimension(style.direction, item.layout.size); + // Only apply the remaining violation for the first flexible item that has a flex grow factor. + const CGFloat flexedStackSize = originalStackSize + currentFlexAdjustment + (i == firstFlexItem && item.child.style.flexGrow > 0 ? remainingViolation : 0); + item.layout = crossChildLayout(item.child, + style, + MAX(flexedStackSize, 0), + MAX(flexedStackSize, 0), + crossDimension(style.direction, sizeRange.min), + crossDimension(style.direction, sizeRange.max), + parentSize); + } + }); + } +} + +/** + https://www.w3.org/TR/css-flexbox-1/#algo-line-break + */ +static std::vector collectChildrenIntoLines(const std::vector &items, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange) +{ + //TODO if infinite max stack size, fast path + if (style.flexWrap == ASStackLayoutFlexWrapNoWrap) { + return std::vector (1, {.items = std::move(items)}); + } + + std::vector lines; + std::vector lineItems; + CGFloat lineStackDimensionSum = 0; + + for(auto it = items.begin(); it != items.end(); ++it) { + const auto &item = *it; + const CGFloat itemStackDimension = stackDimension(style.direction, item.layout.size); + const BOOL negativeViolationIfAddItem = (ASStackUnpositionedLayout::computeStackViolation(lineStackDimensionSum + itemStackDimension, style, sizeRange) < 0); + const BOOL breakCurrentLine = negativeViolationIfAddItem && !lineItems.empty(); + + if (breakCurrentLine) { + lines.push_back({.items = std::vector (lineItems)}); + lineItems.clear(); + lineStackDimensionSum = 0; + } + + lineItems.push_back(std::move(item)); + lineStackDimensionSum += itemStackDimension; + } + + // Handle last line + lines.push_back({.items = std::vector (lineItems)}); + + return lines; +} + +/** + Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and + stretched. + */ +static void layoutItemsAlongUnconstrainedStackDimension(std::vector &items, + const ASStackLayoutSpecStyle &style, + const BOOL concurrent, + const ASSizeRange &sizeRange, + const CGSize parentSize, + const BOOL useOptimizedFlexing) +{ + const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); + const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); + + dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { + auto &item = items[i]; + if (useOptimizedFlexing && isFlexibleInBothDirections(item.child)) { + item.layout = [ASLayout layoutWithLayoutElement:item.child.element size:{0, 0}]; + } else { + item.layout = crossChildLayout(item.child, + style, + ASDimensionResolve(item.child.style.flexBasis, stackDimension(style.direction, parentSize), 0), + ASDimensionResolve(item.child.style.flexBasis, stackDimension(style.direction, parentSize), INFINITY), + minCrossDimension, + maxCrossDimension, + parentSize); + } + }); +} + +ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector &children, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange, + const BOOL concurrent) +{ + if (children.empty()) { + return {}; + } + + // If we have a fixed size in either dimension, pass it to children so they can resolve percentages against it. + // Otherwise, we pass ASLayoutElementParentDimensionUndefined since it will depend on the content. + const CGSize parentSize = { + (sizeRange.min.width == sizeRange.max.width) ? sizeRange.min.width : ASLayoutElementParentDimensionUndefined, + (sizeRange.min.height == sizeRange.max.height) ? sizeRange.min.height : ASLayoutElementParentDimensionUndefined, + }; + + // We may be able to avoid some redundant layout passes + const BOOL optimizedFlexing = useOptimizedFlexing(children, style, sizeRange); + + std::vector items = AS::map(children, [&](const ASStackLayoutSpecChild &child) -> ASStackLayoutSpecItem { + return {child, nil}; + }); + + // We do a first pass of all the children, generating an unpositioned layout for each with an unbounded range along + // the stack dimension. This allows us to compute the "intrinsic" size of each child and find the available violation + // which determines whether we must grow or shrink the flexible children. + layoutItemsAlongUnconstrainedStackDimension(items, + style, + concurrent, + sizeRange, + parentSize, + optimizedFlexing); + + // Collect items into lines (https://www.w3.org/TR/css-flexbox-1/#algo-line-break) + std::vector lines = collectChildrenIntoLines(items, style, sizeRange); + + // Resolve the flexible lengths (https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths) + flexLinesAlongStackDimension(lines, style, concurrent, sizeRange, parentSize, optimizedFlexing); + + // Calculate the cross size of each flex line (https://www.w3.org/TR/css-flexbox-1/#algo-cross-line) + computeLinesCrossSizeAndBaseline(lines, style, sizeRange); + + // Handle 'align-content: stretch' (https://www.w3.org/TR/css-flexbox-1/#algo-line-stretch) + // Determine the used cross size of each item (https://www.w3.org/TR/css-flexbox-1/#algo-stretch) + stretchLinesAlongCrossDimension(lines, style, concurrent, sizeRange, parentSize); + + // Compute stack dimension sum of each line and the whole stack + CGFloat layoutStackDimensionSum = 0; + for (auto &line : lines) { + line.stackDimensionSum = computeItemsStackDimensionSum(line.items, style); + // layoutStackDimensionSum is the max stackDimensionSum among all lines + layoutStackDimensionSum = MAX(line.stackDimensionSum, layoutStackDimensionSum); + } + // Compute cross dimension sum of the stack. + // This should be done before `lines` are moved to a new ASStackUnpositionedLayout struct (i.e `std::move(lines)`) + CGFloat layoutCrossDimensionSum = computeLinesCrossDimensionSum(lines); + + return {.lines = std::move(lines), .stackDimensionSum = layoutStackDimensionSum, .crossDimensionSum = layoutCrossDimensionSum}; +} diff --git a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.h b/Source/Private/_ASCoreAnimationExtras.h similarity index 92% rename from AsyncDisplayKit/Private/_ASCoreAnimationExtras.h rename to Source/Private/_ASCoreAnimationExtras.h index 2998ea5f5a..7dcdf063b6 100644 --- a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.h +++ b/Source/Private/_ASCoreAnimationExtras.h @@ -10,7 +10,7 @@ #import -#import "ASBaseDefines.h" +#import ASDISPLAYNODE_EXTERN_C_BEGIN @@ -53,4 +53,11 @@ extern UIViewContentMode ASDisplayNodeUIContentModeFromCAContentsGravity(NSStrin */ extern UIImage *ASDisplayNodeStretchableBoxContentsWithColor(UIColor *color, CGSize innerSize); +/** + Checks whether a layer has ongoing animations + @param layer A layer to check if animations are ongoing + @return YES if the layer has ongoing animations, otherwise NO + */ +extern BOOL ASDisplayNodeLayerHasAnimations(CALayer *layer); + ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm b/Source/Private/_ASCoreAnimationExtras.mm similarity index 96% rename from AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm rename to Source/Private/_ASCoreAnimationExtras.mm index 64eea95892..d953ba2ff2 100644 --- a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm +++ b/Source/Private/_ASCoreAnimationExtras.mm @@ -8,9 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "_ASCoreAnimationExtras.h" -#import "ASEqualityHelpers.h" -#import "ASAssert.h" +#import +#import +#import extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UIImage *image) { @@ -154,3 +154,8 @@ UIViewContentMode ASDisplayNodeUIContentModeFromCAContentsGravity(NSString *cons // If asserts disabled, fall back to this return UIViewContentModeScaleToFill; } + +BOOL ASDisplayNodeLayerHasAnimations(CALayer *layer) +{ + return (layer.animationKeys.count != 0); +} diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h b/Source/Private/_ASHierarchyChangeSet.h similarity index 83% rename from AsyncDisplayKit/Private/_ASHierarchyChangeSet.h rename to Source/Private/_ASHierarchyChangeSet.h index f2994c2ab2..ef3bf17c38 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h +++ b/Source/Private/_ASHierarchyChangeSet.h @@ -12,7 +12,7 @@ #import #import -#import "ASObjectDescriptionHelpers.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -65,6 +65,7 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); @property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions; @property (nonatomic, strong, readonly) NSIndexSet *indexSet; + @property (nonatomic, readonly) _ASHierarchyChangeType changeType; /** @@ -72,9 +73,11 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); * with type .Insert or .Delete. Calling this on changes of other types is an error. */ - (_ASHierarchySectionChange *)changeByFinalizingType; + @end @interface _ASHierarchyItemChange : NSObject + @property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions; /// Index paths are sorted descending for changeType .Delete, ascending otherwise @@ -89,20 +92,29 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); * with type .Insert or .Delete. Calling this on changes of other types is an error. */ - (_ASHierarchyItemChange *)changeByFinalizingType; + @end @interface _ASHierarchyChangeSet : NSObject -- (instancetype)initWithOldData:(std::vector)oldItemCounts NS_DESIGNATED_INITIALIZER; +/// @precondition The change set must be completed. +@property (nonatomic, strong, readonly) NSIndexSet *deletedSections; +/// @precondition The change set must be completed. +@property (nonatomic, strong, readonly) NSIndexSet *insertedSections; -/** - * The combined completion handler. - * - * @warning The completion block is discarded after reading because it may have captured - * significant resources that we would like to reclaim as soon as possible. - */ -@property (nonatomic, copy, readonly, nullable) void(^completionHandler)(BOOL finished); +@property (nonatomic, readonly) BOOL completed; + +/// Whether or not changes should be animated. +// TODO: if any update in this chagne set is non-animated, the whole update should be non-animated. +@property (nonatomic, readwrite) BOOL animated; + +@property (nonatomic, readonly) BOOL includesReloadData; + +/// Indicates whether the change set is empty, that is it includes neither reload data nor per item or section changes. +@property (nonatomic, readonly) BOOL isEmpty; + +- (instancetype)initWithOldData:(std::vector)oldItemCounts NS_DESIGNATED_INITIALIZER; /** * Append the given completion handler to the combined @c completionHandler. @@ -114,37 +126,43 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); */ - (void)addCompletionHandler:(nullable void(^)(BOOL finished))completion; -/// @precondition The change set must be completed. -@property (nonatomic, strong, readonly) NSIndexSet *deletedSections; -/// @precondition The change set must be completed. -@property (nonatomic, strong, readonly) NSIndexSet *insertedSections; +/** + * Execute the combined completion handler. + * + * @warning The completion block is discarded after reading because it may have captured + * significant resources that we would like to reclaim as soon as possible. + */ +- (void)executeCompletionHandlerWithFinished:(BOOL)finished; /** - Get the section index after the update for the given section before the update. - - @precondition The change set must be completed. - @return The new section index, or NSNotFound if the given section was deleted. + * Get the section index after the update for the given section before the update. + * + * @precondition The change set must be completed. + * @return The new section index, or NSNotFound if the given section was deleted. */ - (NSUInteger)newSectionForOldSection:(NSUInteger)oldSection; -@property (nonatomic, readonly) BOOL completed; - /// Call this once the change set has been constructed to prevent future modifications to the changeset. Calling this more than once is a programmer error. /// NOTE: Calling this method will cause the changeset to convert all reloads into delete/insert pairs. - (void)markCompletedWithNewItemCounts:(std::vector)newItemCounts; - (nullable NSArray <_ASHierarchySectionChange *> *)sectionChangesOfType:(_ASHierarchyChangeType)changeType; + - (nullable NSArray <_ASHierarchyItemChange *> *)itemChangesOfType:(_ASHierarchyChangeType)changeType; /// Returns all item indexes affected by changes of the given type in the given section. - (NSIndexSet *)indexesForItemChangesOfType:(_ASHierarchyChangeType)changeType inSection:(NSUInteger)section; +- (void)reloadData; - (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options; - (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options; - (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options; - (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; - (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; - (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection animationOptions:(ASDataControllerAnimationOptions)options; +- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath animationOptions:(ASDataControllerAnimationOptions)options; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm b/Source/Private/_ASHierarchyChangeSet.mm similarity index 92% rename from AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm rename to Source/Private/_ASHierarchyChangeSet.mm index d46f2acca9..3126b119ad 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm +++ b/Source/Private/_ASHierarchyChangeSet.mm @@ -10,22 +10,32 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "_ASHierarchyChangeSet.h" -#import "ASInternalHelpers.h" -#import "NSIndexSet+ASHelpers.h" -#import "ASAssert.h" -#import "ASDisplayNode+Beta.h" -#import "ASObjectDescriptionHelpers.h" +#import +#import +#import +#import +#import +#import #import +#import +#import -// NOTE: We log before throwing so they don't have to let it bubble up to see the error. -#define ASFailUpdateValidation(...)\ - if ([ASDisplayNode suppressesInvalidCollectionUpdateExceptions]) {\ - NSLog(__VA_ARGS__);\ - } else {\ - NSLog(__VA_ARGS__);\ - ASDisplayNodeFailAssert(__VA_ARGS__);\ - } +// If assertions are enabled and they haven't forced us to suppress the exception, +// then throw, otherwise log. +#if ASDISPLAYNODE_ASSERTIONS_ENABLED + #define ASFailUpdateValidation(...)\ + _Pragma("clang diagnostic push")\ + _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")\ + if ([ASDisplayNode suppressesInvalidCollectionUpdateExceptions]) {\ + NSLog(__VA_ARGS__);\ + } else {\ + NSLog(__VA_ARGS__);\ + [NSException raise:ASCollectionInvalidUpdateException format:__VA_ARGS__];\ + }\ + _Pragma("clang diagnostic pop") +#else + #define ASFailUpdateValidation(...) NSLog(__VA_ARGS__); +#endif BOOL ASHierarchyChangeTypeIsFinal(_ASHierarchyChangeType changeType) { switch (changeType) { @@ -139,11 +149,9 @@ - (instancetype)initWithOldData:(std::vector)oldItemCounts #pragma mark External API -- (void (^)(BOOL finished))completionHandler +- (BOOL)isEmpty { - void (^completionHandler)(BOOL) = _completionHandler; - _completionHandler = nil; - return completionHandler; + return (! _includesReloadData) && (! [self _includesPerItemOrSectionChanges]); } - (void)addCompletionHandler:(void (^)(BOOL))completion @@ -162,6 +170,14 @@ - (void)addCompletionHandler:(void (^)(BOOL))completion }; } +- (void)executeCompletionHandlerWithFinished:(BOOL)finished +{ + if (_completionHandler != nil) { + _completionHandler(finished); + _completionHandler = nil; + } +} + - (void)markCompletedWithNewItemCounts:(std::vector)newItemCounts { NSAssert(!_completed, @"Attempt to mark already-completed changeset as completed."); @@ -235,6 +251,13 @@ - (NSUInteger)newSectionForOldSection:(NSUInteger)oldSection return newIndex; } +- (void)reloadData +{ + [self _ensureNotCompleted]; + NSAssert(_includesReloadData == NO, @"Attempt to reload data multiple times %@", self); + _includesReloadData = YES; +} + - (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options { [self _ensureNotCompleted]; @@ -277,6 +300,24 @@ - (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataController [_reloadSectionChanges addObject:change]; } +- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath animationOptions:(ASDataControllerAnimationOptions)options +{ + /** + * TODO: Proper move implementation. + */ + [self deleteItems:@[ indexPath ] animationOptions:options]; + [self insertItems:@[ newIndexPath ] animationOptions:options]; +} + +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection animationOptions:(ASDataControllerAnimationOptions)options +{ + /** + * TODO: Proper move implementation. + */ + [self deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:options]; + [self insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:options]; +} + #pragma mark Private - (BOOL)_ensureNotCompleted @@ -293,6 +334,10 @@ - (BOOL)_ensureCompleted - (void)_sortAndCoalesceChangeArrays { + if (_includesReloadData) { + return; + } + @autoreleasepool { // Split reloaded sections into [delete(oldIndex), insert(newIndex)] @@ -383,6 +428,14 @@ - (void)_sortAndCoalesceChangeArrays - (void)_validateUpdate { + // If reloadData exists, ignore other changes + if (_includesReloadData) { + if ([self _includesPerItemOrSectionChanges]) { + NSLog(@"Warning: A reload data shouldn't be used in conjuntion with other updates."); + } + return; + } + NSIndexSet *allReloadedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_reloadSectionChanges]; NSInteger newSectionCount = _newItemCounts.size(); @@ -493,6 +546,13 @@ - (void)_validateUpdate } } +- (BOOL)_includesPerItemOrSectionChanges +{ + return 0 < (_originalDeleteSectionChanges.count + _originalDeleteItemChanges.count + +_originalInsertSectionChanges.count + _originalInsertItemChanges.count + + _reloadSectionChanges.count + _reloadItemChanges.count); +} + #pragma mark - Debugging (Private) - (NSString *)description @@ -508,6 +568,7 @@ - (NSString *)debugDescription - (NSMutableArray *)propertiesForDescription { NSMutableArray *result = [NSMutableArray array]; + [result addObject:@{ @"includesReloadData" : @(_includesReloadData) }]; if (_reloadSectionChanges.count > 0) { [result addObject:@{ @"reloadSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_reloadSectionChanges] }]; } diff --git a/AsyncDisplayKit/Private/_ASPendingState.h b/Source/Private/_ASPendingState.h similarity index 96% rename from AsyncDisplayKit/Private/_ASPendingState.h rename to Source/Private/_ASPendingState.h index 1636ea94d3..4cdcfb61f5 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.h +++ b/Source/Private/_ASPendingState.h @@ -10,7 +10,7 @@ #import -#import "UIView+ASConvenience.h" +#import /** diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm similarity index 96% rename from AsyncDisplayKit/Private/_ASPendingState.mm rename to Source/Private/_ASPendingState.mm index c01470e258..0baad11298 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -8,13 +8,13 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "_ASPendingState.h" +#import -#import "_ASCoreAnimationExtras.h" -#import "_ASAsyncTransactionContainer.h" -#import "ASAssert.h" -#import "ASInternalHelpers.h" -#import "ASDisplayNodeInternal.h" +#import +#import +#import +#import +#import #define __shouldSetNeedsDisplay(layer) (flags.needsDisplay \ || (flags.setOpaque && opaque != (layer).opaque)\ @@ -24,7 +24,8 @@ // Properties int needsDisplay:1; int needsLayout:1; - + int layoutIfNeeded:1; + // Flags indicating that a given property should be applied to the view at creation int setClipsToBounds:1; int setOpaque:1; @@ -272,6 +273,11 @@ - (void)setNeedsLayout _flags.needsLayout = YES; } +- (void)layoutIfNeeded +{ + _flags.layoutIfNeeded = YES; +} + - (void)setClipsToBounds:(BOOL)flag { clipsToBounds = flag; @@ -761,9 +767,6 @@ - (void)applyToLayer:(CALayer *)layer if (flags.setEdgeAntialiasingMask) layer.edgeAntialiasingMask = edgeAntialiasingMask; - if (flags.needsLayout) - [layer setNeedsLayout]; - if (flags.setAsyncTransactionContainer) layer.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; @@ -771,6 +774,12 @@ - (void)applyToLayer:(CALayer *)layer ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired"); ASPendingStateApplyMetricsToLayer(self, layer); + + if (flags.needsLayout) + [layer setNeedsLayout]; + + if (flags.layoutIfNeeded) + [layer layoutIfNeeded]; } - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPropertiesHandling @@ -889,9 +898,6 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr if (flags.setEdgeAntialiasingMask) layer.edgeAntialiasingMask = edgeAntialiasingMask; - if (flags.needsLayout) - [view setNeedsLayout]; - if (flags.setAsyncTransactionContainer) view.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; @@ -947,14 +953,20 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr if (flags.setFrame && specialPropertiesHandling) { // Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform -#if DEBUG - // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. - ASDisplayNodeAssert(CATransform3DIsIdentity(layer.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); -#endif +//#if DEBUG +// // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. +// ASDisplayNodeAssert(CATransform3DIsIdentity(layer.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); +//#endif view.frame = frame; } else { ASPendingStateApplyMetricsToLayer(self, layer); } + + if (flags.needsLayout) + [view setNeedsLayout]; + + if (flags.layoutIfNeeded) + [view layoutIfNeeded]; } // FIXME: Make this more efficient by tracking which properties are set rather than reading everything. diff --git a/AsyncDisplayKit/Private/_ASScopeTimer.h b/Source/Private/_ASScopeTimer.h similarity index 100% rename from AsyncDisplayKit/Private/_ASScopeTimer.h rename to Source/Private/_ASScopeTimer.h diff --git a/AsyncDisplayKit/TextKit/ASLayoutManager.h b/Source/TextKit/ASLayoutManager.h similarity index 86% rename from AsyncDisplayKit/TextKit/ASLayoutManager.h rename to Source/TextKit/ASLayoutManager.h index b8a62b3ca5..c7664818bf 100644 --- a/AsyncDisplayKit/TextKit/ASLayoutManager.h +++ b/Source/TextKit/ASLayoutManager.h @@ -9,7 +9,9 @@ // #import +#import +AS_SUBCLASSING_RESTRICTED @interface ASLayoutManager : NSLayoutManager @end diff --git a/AsyncDisplayKit/TextKit/ASLayoutManager.m b/Source/TextKit/ASLayoutManager.m similarity index 96% rename from AsyncDisplayKit/TextKit/ASLayoutManager.m rename to Source/TextKit/ASLayoutManager.m index 46252c3cab..bc18e7142e 100644 --- a/AsyncDisplayKit/TextKit/ASLayoutManager.m +++ b/Source/TextKit/ASLayoutManager.m @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASLayoutManager.h" +#import @implementation ASLayoutManager diff --git a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h b/Source/TextKit/ASTextKitAttributes.h similarity index 96% rename from AsyncDisplayKit/TextKit/ASTextKitAttributes.h rename to Source/TextKit/ASTextKitAttributes.h index 0021ec94cb..e4258a2be4 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h +++ b/Source/TextKit/ASTextKitAttributes.h @@ -11,9 +11,7 @@ #pragma once #import -#import "ASEqualityHelpers.h" - -@protocol ASTextKitTruncating; +#import extern NSString *const ASTextKitTruncationAttributeName; /** @@ -110,7 +108,8 @@ struct ASTextKitAttributes { && maximumNumberOfLines == other.maximumNumberOfLines && shadowOpacity == other.shadowOpacity && shadowRadius == other.shadowRadius - && [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors] + && (pointSizeScaleFactors == other.pointSizeScaleFactors + || [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors]) && CGSizeEqualToSize(shadowOffset, other.shadowOffset) && ASObjectIsEqual(exclusionPaths, other.exclusionPaths) && ASObjectIsEqual(avoidTailTruncationSet, other.avoidTailTruncationSet) diff --git a/AsyncDisplayKit/TextKit/ASTextKitAttributes.mm b/Source/TextKit/ASTextKitAttributes.mm similarity index 91% rename from AsyncDisplayKit/TextKit/ASTextKitAttributes.mm rename to Source/TextKit/ASTextKitAttributes.mm index 5ec939c255..e78ad258a2 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitAttributes.mm +++ b/Source/TextKit/ASTextKitAttributes.mm @@ -8,9 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitAttributes.h" +#import -#import "ASEqualityHashHelpers.h" +#import #include diff --git a/AsyncDisplayKit/TextKit/ASTextKitComponents.h b/Source/TextKit/ASTextKitComponents.h similarity index 95% rename from AsyncDisplayKit/TextKit/ASTextKitComponents.h rename to Source/TextKit/ASTextKitComponents.h index 9da19d7061..6cf94ba361 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitComponents.h +++ b/Source/TextKit/ASTextKitComponents.h @@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN +AS_SUBCLASSING_RESTRICTED @interface ASTextKitComponents : NSObject /** @@ -44,6 +45,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth; + +- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth + forMaxNumberOfLines:(NSInteger)numberOfLines; + @property (nonatomic, strong, readonly) NSTextStorage *textStorage; @property (nonatomic, strong, readonly) NSTextContainer *textContainer; @property (nonatomic, strong, readonly) NSLayoutManager *layoutManager; diff --git a/AsyncDisplayKit/TextKit/ASTextKitComponents.m b/Source/TextKit/ASTextKitComponents.mm similarity index 58% rename from AsyncDisplayKit/TextKit/ASTextKitComponents.m rename to Source/TextKit/ASTextKitComponents.mm index 93eb429ba4..f7810b63f2 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitComponents.m +++ b/Source/TextKit/ASTextKitComponents.mm @@ -8,7 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitComponents.h" +#import + +#import @interface ASTextKitComponents () @@ -66,4 +68,55 @@ - (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth return textSize; } +- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth + forMaxNumberOfLines:(NSInteger)maxNumberOfLines +{ + if (maxNumberOfLines == 0) { + return [self sizeForConstrainedWidth:constrainedWidth]; + } + + ASTextKitComponents *components = self; + + // Always use temporary stack in case of threading issues + components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, CGFLOAT_MAX)]; + + // Force glyph generation and layout, which may not have happened yet (and isn't triggered by - usedRectForTextContainer:). + [components.layoutManager ensureLayoutForTextContainer:components.textContainer]; + + CGFloat width = [components.layoutManager usedRectForTextContainer:components.textContainer].size.width; + + // Calculate height based on line fragments + // Based on calculating number of lines from: http://asciiwwdc.com/2013/sessions/220 + NSRange glyphRange, lineRange = NSMakeRange(0, 0); + CGRect rect = CGRectZero; + CGFloat height = 0; + CGFloat lastOriginY = -1.0; + NSUInteger numberOfLines = 0; + + glyphRange = [components.layoutManager glyphRangeForTextContainer:components.textContainer]; + + while (lineRange.location < NSMaxRange(glyphRange)) { + rect = [components.layoutManager lineFragmentRectForGlyphAtIndex:lineRange.location + effectiveRange:&lineRange]; + + if (CGRectGetMinY(rect) > lastOriginY) { + ++numberOfLines; + if (numberOfLines == maxNumberOfLines) { + height = rect.origin.y + rect.size.height; + break; + } + } + + lastOriginY = CGRectGetMinY(rect); + lineRange.location = NSMaxRange(lineRange); + } + + CGFloat fragmentHeight = rect.origin.y + rect.size.height; + CGFloat finalHeight = std::ceil(std::fmax(height, fragmentHeight)); + + CGSize size = CGSizeMake(width, finalHeight); + + return size; +} + @end diff --git a/AsyncDisplayKit/TextKit/ASTextKitContext.h b/Source/TextKit/ASTextKitContext.h similarity index 91% rename from AsyncDisplayKit/TextKit/ASTextKitContext.h rename to Source/TextKit/ASTextKitContext.h index 58257efbab..3373e08546 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitContext.h +++ b/Source/TextKit/ASTextKitContext.h @@ -9,6 +9,7 @@ // #import +#import /** A threadsafe container for the TextKit components that ASTextKit uses to lay out and truncate its text. @@ -16,6 +17,7 @@ This container is the sole owner and manager of the TextKit classes. This is an important model because of major thread safety issues inside vanilla TextKit. It provides a central locking location for accessing TextKit methods. */ +AS_SUBCLASSING_RESTRICTED @interface ASTextKitContext : NSObject /** @@ -29,8 +31,6 @@ exclusionPaths:(NSArray *)exclusionPaths constrainedSize:(CGSize)constrainedSize; -@property (nonatomic, assign, readwrite) CGSize constrainedSize; - /** All operations on TextKit values MUST occur within this locked context. Simultaneous access (even non-mutative) to TextKit components may cause crashes. @@ -40,7 +40,7 @@ Callers MUST NOT keep a ref to these internal objects and use them later. This WILL cause crashes in your application. */ -- (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *layoutManager, +- (void)performBlockWithLockedTextKitComponents:(AS_NOESCAPE void (^)(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer))block; diff --git a/AsyncDisplayKit/TextKit/ASTextKitContext.mm b/Source/TextKit/ASTextKitContext.mm similarity index 81% rename from AsyncDisplayKit/TextKit/ASTextKitContext.mm rename to Source/TextKit/ASTextKitContext.mm index ba7477c500..79f40c5813 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitContext.mm +++ b/Source/TextKit/ASTextKitContext.mm @@ -8,9 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitContext.h" -#import "ASLayoutManager.h" -#import "ASThread.h" +#import +#import +#import #include @@ -39,10 +39,18 @@ - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString __instanceLock__ = std::make_shared(); // Create the TextKit component stack with our default configuration. - _textStorage = (attributedString ? [[NSTextStorage alloc] initWithAttributedString:attributedString] : [[NSTextStorage alloc] init]); + + _textStorage = [[NSTextStorage alloc] init]; _layoutManager = [[ASLayoutManager alloc] init]; _layoutManager.usesFontLeading = NO; [_textStorage addLayoutManager:_layoutManager]; + + // Instead of calling [NSTextStorage initWithAttributedString:], setting attributedString just after calling addlayoutManager can fix CJK language layout issues. + // See https://github.com/facebook/AsyncDisplayKit/issues/2894 + if (attributedString) { + [_textStorage setAttributedString:attributedString]; + } + _textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize]; // We want the text laid out up to the very edges of the container. _textContainer.lineFragmentPadding = 0; @@ -54,18 +62,6 @@ - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString return self; } -- (CGSize)constrainedSize -{ - ASDN::MutexSharedLocker l(__instanceLock__); - return _textContainer.size; -} - -- (void)setConstrainedSize:(CGSize)constrainedSize -{ - ASDN::MutexSharedLocker l(__instanceLock__); - _textContainer.size = constrainedSize; -} - - (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *, NSTextStorage *, NSTextContainer *))block diff --git a/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.h b/Source/TextKit/ASTextKitCoreTextAdditions.h similarity index 100% rename from AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.h rename to Source/TextKit/ASTextKitCoreTextAdditions.h diff --git a/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.m b/Source/TextKit/ASTextKitCoreTextAdditions.m similarity index 99% rename from AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.m rename to Source/TextKit/ASTextKitCoreTextAdditions.m index d157cb4797..4e90fefd44 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.m +++ b/Source/TextKit/ASTextKitCoreTextAdditions.m @@ -8,12 +8,12 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitCoreTextAdditions.h" +#import #import #import -#import "ASAssert.h" +#import #pragma mark - Public BOOL ASAttributeWithNameIsUnsupportedCoreTextAttribute(NSString *attributeName) diff --git a/AsyncDisplayKit/TextKit/ASTextKitEntityAttribute.h b/Source/TextKit/ASTextKitEntityAttribute.h similarity index 94% rename from AsyncDisplayKit/TextKit/ASTextKitEntityAttribute.h rename to Source/TextKit/ASTextKitEntityAttribute.h index 7258d1031f..6b28b5540e 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitEntityAttribute.h +++ b/Source/TextKit/ASTextKitEntityAttribute.h @@ -9,6 +9,7 @@ // #import +#import /** The object that should be embedded with ASTextKitEntityAttributeName. Please note that the entity you provide MUST @@ -19,6 +20,7 @@ rdar://19352367 */ +AS_SUBCLASSING_RESTRICTED @interface ASTextKitEntityAttribute : NSObject @property (nonatomic, strong, readonly) id entity; diff --git a/AsyncDisplayKit/TextKit/ASTextKitEntityAttribute.m b/Source/TextKit/ASTextKitEntityAttribute.m similarity index 94% rename from AsyncDisplayKit/TextKit/ASTextKitEntityAttribute.m rename to Source/TextKit/ASTextKitEntityAttribute.m index fab55bce5d..0b2aaa2d96 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitEntityAttribute.m +++ b/Source/TextKit/ASTextKitEntityAttribute.m @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitEntityAttribute.h" +#import @implementation ASTextKitEntityAttribute diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.h b/Source/TextKit/ASTextKitFontSizeAdjuster.h similarity index 89% rename from AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.h rename to Source/TextKit/ASTextKitFontSizeAdjuster.h index 38009494d8..218c355231 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.h +++ b/Source/TextKit/ASTextKitFontSizeAdjuster.h @@ -8,10 +8,15 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import -#import "ASTextKitAttributes.h" -#import "ASTextKitContext.h" +#import +#import +#import +NS_ASSUME_NONNULL_BEGIN + +@class ASTextKitContext; + +AS_SUBCLASSING_RESTRICTED @interface ASTextKitFontSizeAdjuster : NSObject @property (nonatomic, assign) CGSize constrainedSize; @@ -45,4 +50,4 @@ @end - +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm b/Source/TextKit/ASTextKitFontSizeAdjuster.mm similarity index 98% rename from AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm rename to Source/TextKit/ASTextKitFontSizeAdjuster.mm index 81329d1e7e..171ae32566 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm +++ b/Source/TextKit/ASTextKitFontSizeAdjuster.mm @@ -9,13 +9,13 @@ // -#import "ASTextKitFontSizeAdjuster.h" +#import #import #import -#import "ASTextKitContext.h" -#import "ASLayoutManager.h" +#import +#import //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.h b/Source/TextKit/ASTextKitRenderer+Positioning.h similarity index 98% rename from AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.h rename to Source/TextKit/ASTextKitRenderer+Positioning.h index 48543b7193..e298de9452 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.h +++ b/Source/TextKit/ASTextKitRenderer+Positioning.h @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitRenderer.h" +#import typedef void (^as_text_component_index_block_t)(NSUInteger characterIndex, CGRect glyphBoundingRect, diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm b/Source/TextKit/ASTextKitRenderer+Positioning.mm similarity index 99% rename from AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm rename to Source/TextKit/ASTextKitRenderer+Positioning.mm index 1039f86494..1b03282781 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm +++ b/Source/TextKit/ASTextKitRenderer+Positioning.mm @@ -8,15 +8,15 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitRenderer+Positioning.h" +#import #import #import -#import "ASAssert.h" +#import -#import "ASTextKitContext.h" -#import "ASTextKitShadower.h" +#import +#import static const CGFloat ASTextKitRendererGlyphTouchHitSlop = 5.0; static const CGFloat ASTextKitRendererTextCapHeightPadding = 1.3; diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer+TextChecking.h b/Source/TextKit/ASTextKitRenderer+TextChecking.h similarity index 95% rename from AsyncDisplayKit/TextKit/ASTextKitRenderer+TextChecking.h rename to Source/TextKit/ASTextKitRenderer+TextChecking.h index 55a0f2d515..32dc02f3d5 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer+TextChecking.h +++ b/Source/TextKit/ASTextKitRenderer+TextChecking.h @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitRenderer.h" +#import /** Application extensions to NSTextCheckingType. We're allowed to do this (see NSTextCheckingAllCustomTypes). diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer+TextChecking.mm b/Source/TextKit/ASTextKitRenderer+TextChecking.mm similarity index 94% rename from AsyncDisplayKit/TextKit/ASTextKitRenderer+TextChecking.mm rename to Source/TextKit/ASTextKitRenderer+TextChecking.mm index 23b181f8bc..5de148cc22 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer+TextChecking.mm +++ b/Source/TextKit/ASTextKitRenderer+TextChecking.mm @@ -8,11 +8,11 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitRenderer+TextChecking.h" +#import -#import "ASTextKitEntityAttribute.h" -#import "ASTextKitRenderer+Positioning.h" -#import "ASTextKitTailTruncater.h" +#import +#import +#import @implementation ASTextKitTextCheckingResult diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.h b/Source/TextKit/ASTextKitRenderer.h similarity index 96% rename from AsyncDisplayKit/TextKit/ASTextKitRenderer.h rename to Source/TextKit/ASTextKitRenderer.h index d34ce0e1c5..61b0b231ac 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.h +++ b/Source/TextKit/ASTextKitRenderer.h @@ -12,7 +12,7 @@ #import -#import "ASTextKitAttributes.h" +#import @class ASTextKitContext; @class ASTextKitShadower; @@ -85,6 +85,11 @@ */ - (NSUInteger)lineCount; +/** + Whether or not the text is truncated. + */ +- (BOOL)isTruncated; + @end @interface ASTextKitRenderer (ASTextKitRendererConvenience) diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm b/Source/TextKit/ASTextKitRenderer.mm similarity index 86% rename from AsyncDisplayKit/TextKit/ASTextKitRenderer.mm rename to Source/TextKit/ASTextKitRenderer.mm index d4246b2a1c..9a1de9a2aa 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm +++ b/Source/TextKit/ASTextKitRenderer.mm @@ -8,16 +8,16 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitRenderer.h" +#import -#import "ASAssert.h" +#import -#import "ASTextKitContext.h" -#import "ASTextKitShadower.h" -#import "ASTextKitTailTruncater.h" -#import "ASTextKitFontSizeAdjuster.h" -#import "ASInternalHelpers.h" -#import "ASRunLoopQueue.h" +#import +#import +#import +#import +#import +#import //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -37,7 +37,6 @@ @implementation ASTextKitRenderer { CGSize _calculatedSize; - BOOL _sizeIsCalculated; } @synthesize attributes = _attributes, context = _context, shadower = _shadower, truncater = _truncater, fontSizeAdjuster = _fontSizeAdjuster; @@ -49,62 +48,38 @@ - (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)attribute if (self = [super init]) { _constrainedSize = constrainedSize; _attributes = attributes; - _sizeIsCalculated = NO; _currentScaleFactor = 1; - } - return self; -} - -- (ASTextKitShadower *)shadower -{ - if (!_shadower) { - ASTextKitAttributes attributes = _attributes; + + // As the renderer should be thread safe, create all subcomponents in the initialization method _shadower = [ASTextKitShadower shadowerWithShadowOffset:attributes.shadowOffset shadowColor:attributes.shadowColor shadowOpacity:attributes.shadowOpacity shadowRadius:attributes.shadowRadius]; - } - return _shadower; -} - -- (ASTextKitTailTruncater *)truncater -{ - if (!_truncater) { - ASTextKitAttributes attributes = _attributes; - NSCharacterSet *avoidTailTruncationSet = attributes.avoidTailTruncationSet ? : _defaultAvoidTruncationCharacterSet(); + + // We must inset the constrained size by the size of the shadower. + CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; + + _context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString + lineBreakMode:attributes.lineBreakMode + maximumNumberOfLines:attributes.maximumNumberOfLines + exclusionPaths:attributes.exclusionPaths + constrainedSize:shadowConstrainedSize]; + + NSCharacterSet *avoidTailTruncationSet = attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet(); _truncater = [[ASTextKitTailTruncater alloc] initWithContext:[self context] truncationAttributedString:attributes.truncationAttributedString avoidTailTruncationSet:avoidTailTruncationSet]; - } - return _truncater; -} - -- (ASTextKitFontSizeAdjuster *)fontSizeAdjuster -{ - if (!_fontSizeAdjuster) { + ASTextKitAttributes attributes = _attributes; // We must inset the constrained size by the size of the shadower. - CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; _fontSizeAdjuster = [[ASTextKitFontSizeAdjuster alloc] initWithContext:[self context] constrainedSize:shadowConstrainedSize textKitAttributes:attributes]; + + // Calcualate size immediately + [self _calculateSize]; } - return _fontSizeAdjuster; -} - -- (ASTextKitContext *)context -{ - if (!_context) { - ASTextKitAttributes attributes = _attributes; - // We must inset the constrained size by the size of the shadower. - CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; - _context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString - lineBreakMode:attributes.lineBreakMode - maximumNumberOfLines:attributes.maximumNumberOfLines - exclusionPaths:attributes.exclusionPaths - constrainedSize:shadowConstrainedSize]; - } - return _context; + return self; } - (NSStringDrawingContext *)stringDrawingContext @@ -127,10 +102,6 @@ - (NSStringDrawingContext *)stringDrawingContext - (CGSize)size { - if (!_sizeIsCalculated) { - [self _calculateSize]; - _sizeIsCalculated = YES; - } return _calculatedSize; } @@ -222,12 +193,6 @@ - (void)drawInContext:(CGContextRef)context bounds:(CGRect)bounds; { // We add an assertion so we can track the rare conditions where a graphics context is not present ASDisplayNodeAssertNotNil(context, @"This is no good without a context."); - - // This renderer may not be the one that did the sizing. If that is the case its truncation and currentScaleFactor may not have been evaluated. - // If there's any possibility we need to truncate or scale (i.e. width is not infinite), perform the size calculation. - if (_sizeIsCalculated == NO && isinf(_constrainedSize.width) == NO) { - [self _calculateSize]; - } bounds = CGRectIntersection(bounds, { .size = _constrainedSize }); CGRect shadowInsetBounds = [[self shadower] insetRectWithConstrainedRect:bounds]; @@ -242,10 +207,6 @@ - (void)drawInContext:(CGContextRef)context bounds:(CGRect)bounds; // fast path. if (self.canUseFastPath) { CGRect drawingBounds = shadowInsetBounds; - // Add a fudge-factor to the height, to workaround a bug in iOS 7 - if (AS_AT_LEAST_IOS8 == NO) { - drawingBounds.size.height += 3; - } [_attributes.attributedString drawWithRect:drawingBounds options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine context:self.stringDrawingContext]; } else { BOOL isScaled = [self isScaled]; @@ -265,7 +226,7 @@ - (void)drawInContext:(CGContextRef)context bounds:(CGRect)bounds; LOG(@"usedRect: %@", NSStringFromCGRect([layoutManager usedRectForTextContainer:textContainer])); - NSRange glyphRange = [layoutManager glyphRangeForBoundingRect:CGRectMake(0,0,textContainer.size.width, textContainer.size.height) inTextContainer:textContainer]; + NSRange glyphRange = [layoutManager glyphRangeForBoundingRect:(CGRect){ .size = textContainer.size } inTextContainer:textContainer]; LOG(@"boundingRect: %@", NSStringFromCGRect([layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer])); [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; @@ -296,11 +257,21 @@ - (NSUInteger)lineCount return lineCount; } +- (BOOL)isTruncated +{ + if (self.canUseFastPath) { + CGRect boundedRect = [_attributes.attributedString boundingRectWithSize:CGSizeMake(_constrainedSize.width, CGFLOAT_MAX) + options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine + context:nil]; + return boundedRect.size.height > _constrainedSize.height; + } else { + return self.firstVisibleRange.length < _attributes.attributedString.length; + } +} + - (std::vector)visibleRanges { - ASTextKitTailTruncater *truncater = [self truncater]; - [truncater truncate]; - return truncater.visibleRanges; + return _truncater.visibleRanges; } @end diff --git a/AsyncDisplayKit/TextKit/ASTextKitShadower.h b/Source/TextKit/ASTextKitShadower.h similarity index 97% rename from AsyncDisplayKit/TextKit/ASTextKitShadower.h rename to Source/TextKit/ASTextKitShadower.h index 93dd99ffa8..c1f6ebe174 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitShadower.h +++ b/Source/TextKit/ASTextKitShadower.h @@ -9,10 +9,12 @@ // #import +#import /** * @abstract an immutable class for calculating shadow padding drawing a shadowed background for text */ +AS_SUBCLASSING_RESTRICTED @interface ASTextKitShadower : NSObject + (ASTextKitShadower *)shadowerWithShadowOffset:(CGSize)shadowOffset diff --git a/AsyncDisplayKit/TextKit/ASTextKitShadower.mm b/Source/TextKit/ASTextKitShadower.mm similarity index 99% rename from AsyncDisplayKit/TextKit/ASTextKitShadower.mm rename to Source/TextKit/ASTextKitShadower.mm index ab961a3213..4186312a59 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitShadower.mm +++ b/Source/TextKit/ASTextKitShadower.mm @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitShadower.h" +#import #import diff --git a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.h b/Source/TextKit/ASTextKitTailTruncater.h similarity index 79% rename from AsyncDisplayKit/TextKit/ASTextKitTailTruncater.h rename to Source/TextKit/ASTextKitTailTruncater.h index c0de25e368..3588c1eff9 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.h +++ b/Source/TextKit/ASTextKitTailTruncater.h @@ -10,8 +10,10 @@ #import -#import "ASTextKitTruncating.h" +#import +#import +AS_SUBCLASSING_RESTRICTED @interface ASTextKitTailTruncater : NSObject @end diff --git a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm b/Source/TextKit/ASTextKitTailTruncater.mm similarity index 99% rename from AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm rename to Source/TextKit/ASTextKitTailTruncater.mm index 0a83046a2d..c7aabe8e7a 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm +++ b/Source/TextKit/ASTextKitTailTruncater.mm @@ -8,8 +8,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitContext.h" -#import "ASTextKitTailTruncater.h" +#import +#import @implementation ASTextKitTailTruncater { diff --git a/AsyncDisplayKit/TextKit/ASTextKitTruncating.h b/Source/TextKit/ASTextKitTruncating.h similarity index 96% rename from AsyncDisplayKit/TextKit/ASTextKitTruncating.h rename to Source/TextKit/ASTextKitTruncating.h index 952be079eb..05cabfda25 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitTruncating.h +++ b/Source/TextKit/ASTextKitTruncating.h @@ -8,14 +8,13 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #import -#import - -#import "ASTextKitRenderer.h" - NS_ASSUME_NONNULL_BEGIN +@class ASTextKitContext; + @protocol ASTextKitTruncating /** diff --git a/AsyncDisplayKit/TextKit/ASTextNodeTypes.h b/Source/TextKit/ASTextNodeTypes.h similarity index 100% rename from AsyncDisplayKit/TextKit/ASTextNodeTypes.h rename to Source/TextKit/ASTextNodeTypes.h diff --git a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h b/Source/TextKit/ASTextNodeWordKerner.h similarity index 96% rename from AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h rename to Source/TextKit/ASTextNodeWordKerner.h index 58df9cc83d..af76500567 100644 --- a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h +++ b/Source/TextKit/ASTextNodeWordKerner.h @@ -10,6 +10,7 @@ #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -18,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN @discussion Its current job is word kerning, i.e. adjusting the width of spaces to match the set wordKernedSpaceWidth. If word kerning is not needed, set the layoutManager's delegate to nil. */ +AS_SUBCLASSING_RESTRICTED @interface ASTextNodeWordKerner : NSObject /** diff --git a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m b/Source/TextKit/ASTextNodeWordKerner.m similarity index 98% rename from AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m rename to Source/TextKit/ASTextNodeWordKerner.m index 2cf5cb7f41..7fae924da2 100644 --- a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m +++ b/Source/TextKit/ASTextNodeWordKerner.m @@ -8,11 +8,11 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextNodeWordKerner.h" +#import #import -#import "ASTextNodeTypes.h" +#import @implementation ASTextNodeWordKerner diff --git a/AsyncDisplayKit/UIImage+ASConvenience.h b/Source/UIImage+ASConvenience.h similarity index 57% rename from AsyncDisplayKit/UIImage+ASConvenience.h rename to Source/UIImage+ASConvenience.h index 0dc0668115..1c89ccf4cd 100644 --- a/AsyncDisplayKit/UIImage+ASConvenience.h +++ b/Source/UIImage+ASConvenience.h @@ -10,20 +10,57 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import -#import +#import #import NS_ASSUME_NONNULL_BEGIN -// High-performance flat-colored, rounded-corner resizable images -// -// For "Baked-in Opaque" corners, set cornerColor equal to the color behind the rounded image object, i.e. the background color. -// For "Baked-in Alpha" corners, set cornerColor = [UIColor clearColor] -// -// See http://asyncdisplaykit.org/docs/corner-rounding.html for an explanation. +/** + * Dramatically faster version of +[UIImage imageNamed:]. Although it is believed that imageNamed: + * has a cache and is fast, it actually performs expensive asset catalog lookups and is often a + * performance bottleneck (verified on iOS 7 through iOS 10). + * + * Use [UIImage as_imageNamed:] anywhere in your app, even if you aren't using other parts of ASDK. + * Although not the best choice for extremely large assets that are only used once, it is the ideal + * choice for any assets used in tab bars, nav bars, buttons, table or collection cells, etc. + */ + +@interface UIImage (ASDKFastImageNamed) -@interface UIImage (ASDKAdditions) +/** + * A version of imageNamed that caches results because loading an image is expensive. + * Calling with the same name value will usually return the same object. A UIImage, + * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. + * + * @param imageName The name of the image to load + * @return The loaded image or nil + */ ++ (UIImage *)as_imageNamed:(NSString *)imageName; + +/** + * A version of imageNamed that caches results because loading an image is expensive. + * Calling with the same name value will usually return the same object. A UIImage, + * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. + * + * @param imageName The name of the image to load + * @param traitCollection The traits associated with the intended environment for the image. + * @return The loaded image or nil + */ ++ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection; + +@end + +/** + * High-performance flat-colored, rounded-corner resizable images + * + * For "Baked-in Opaque" corners, set cornerColor equal to the color behind the rounded image object, + * i.e. the background color. + * For "Baked-in Alpha" corners, set cornerColor = [UIColor clearColor] + * + * See http://asyncdisplaykit.org/docs/corner-rounding.html for an explanation. + */ + +@interface UIImage (ASDKResizableRoundedRects) /** * This generates a flat-color, rounded-corner resizeable image @@ -33,7 +70,7 @@ NS_ASSUME_NONNULL_BEGIN * @param fillColor The fill color of the rounded-corner image */ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(UIColor *)cornerColor + cornerColor:(nullable UIColor *)cornerColor fillColor:(UIColor *)fillColor AS_WARN_UNUSED_RESULT; /** @@ -63,7 +100,7 @@ NS_ASSUME_NONNULL_BEGIN * @param scale The number of pixels per point. Provide 0.0 to use the screen scale. */ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(UIColor *)cornerColor + cornerColor:(nullable UIColor *)cornerColor fillColor:(UIColor *)fillColor borderColor:(nullable UIColor *)borderColor borderWidth:(CGFloat)borderWidth diff --git a/AsyncDisplayKit/UIImage+ASConvenience.m b/Source/UIImage+ASConvenience.m similarity index 72% rename from AsyncDisplayKit/UIImage+ASConvenience.m rename to Source/UIImage+ASConvenience.m index b6ae5adba1..4d1b5985c9 100644 --- a/AsyncDisplayKit/UIImage+ASConvenience.m +++ b/Source/UIImage+ASConvenience.m @@ -10,12 +10,60 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "UIImage+ASConvenience.h" -#import -#import "ASInternalHelpers.h" -#import "ASAssert.h" +#import +#import +#import -@implementation UIImage (ASDKAdditions) +#pragma mark - ASDKFastImageNamed + +@implementation UIImage (ASDKFastImageNamed) + +UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollection) +{ + static NSCache *imageCache = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // Because NSCache responds to memory warnings, we do not need an explicit limit. + // all of these objects contain compressed image data and are relatively small + // compared to the backing stores of text and image views. + imageCache = [[NSCache alloc] init]; + }); + + UIImage *image = nil; + if ([imageName length] > 0) { + NSString *imageKey = imageName; + if (traitCollection) { + char imageKeyBuffer[256]; + snprintf(imageKeyBuffer, sizeof(imageKeyBuffer), "%s|%ld|%ld", imageName.UTF8String, (long)traitCollection.horizontalSizeClass, (long)traitCollection.verticalSizeClass); + imageKey = [NSString stringWithUTF8String:imageKeyBuffer]; + } + + image = [imageCache objectForKey:imageKey]; + if (!image) { + image = [UIImage imageNamed:imageName inBundle:nil compatibleWithTraitCollection:traitCollection]; + if (image) { + [imageCache setObject:image forKey:imageKey]; + } + } + } + return image; +} + ++ (UIImage *)as_imageNamed:(NSString *)imageName +{ + return cachedImageNamed(imageName, nil); +} + ++ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(UITraitCollection *)traitCollection +{ + return cachedImageNamed(imageName, traitCollection); +} + +@end + +#pragma mark - ASDKResizableRoundedRects + +@implementation UIImage (ASDKResizableRoundedRects) + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius cornerColor:(UIColor *)cornerColor @@ -76,9 +124,9 @@ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius PathKey key = { roundedCorners, cornerRadius }; NSValue *pathKeyObject = [[NSValue alloc] initWithBytes:&key objCType:@encode(PathKey)]; + CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius); UIBezierPath *path = [__pathCache objectForKey:pathKeyObject]; if (path == nil) { - CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius); path = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:roundedCorners cornerRadii:cornerRadii]; [__pathCache setObject:path forKey:pathKeyObject]; } @@ -91,7 +139,7 @@ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius if (cornerColor) { contextIsClean = NO; [cornerColor setFill]; - // Copy "blend" mode is extra fast because it disregards any value currently in the buffer and overwrites directly. + // Copy "blend" mode is extra fast because it disregards any value currently in the buffer and overrides directly. UIRectFillUsingBlendMode(bounds, kCGBlendModeCopy); } @@ -107,7 +155,9 @@ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius // It is rarer to have a stroke path, and our cache key only handles rounded rects for the exact-stretchable // size calculated by cornerRadius, so we won't bother caching this path. Profiling validates this decision. - UIBezierPath *strokePath = [UIBezierPath bezierPathWithRoundedRect:strokeRect cornerRadius:cornerRadius]; + UIBezierPath *strokePath = [UIBezierPath bezierPathWithRoundedRect:strokeRect + byRoundingCorners:roundedCorners + cornerRadii:cornerRadii]; [strokePath setLineWidth:borderWidth]; BOOL canUseCopy = (CGColorGetAlpha(borderColor.CGColor) == 1); [strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; diff --git a/Source/UIResponder+AsyncDisplayKit.h b/Source/UIResponder+AsyncDisplayKit.h new file mode 100644 index 0000000000..c39b5275a9 --- /dev/null +++ b/Source/UIResponder+AsyncDisplayKit.h @@ -0,0 +1,24 @@ +// +// UIResponder+AsyncDisplayKit.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/13/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIResponder (AsyncDisplayKit) + +/** + * The nearest view controller above this responder, if one exists. + * + * This property must be accessed on the main thread. + */ +@property (nonatomic, nullable, readonly) __kindof UIViewController *asdk_associatedViewController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/UIResponder+AsyncDisplayKit.m b/Source/UIResponder+AsyncDisplayKit.m new file mode 100644 index 0000000000..55e4c9fbfa --- /dev/null +++ b/Source/UIResponder+AsyncDisplayKit.m @@ -0,0 +1,31 @@ +// +// UIResponder+AsyncDisplayKit.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/13/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "UIResponder+AsyncDisplayKit.h" + +#import +#import +#import + +@implementation UIResponder (AsyncDisplayKit) + +- (__kindof UIViewController *)asdk_associatedViewController +{ + ASDisplayNodeAssertMainThread(); + + for (UIResponder *responder in [self asdk_responderChainEnumerator]) { + UIViewController *vc = ASDynamicCast(responder, UIViewController); + if (vc) { + return vc; + } + } + return nil; +} + +@end + diff --git a/AsyncDisplayKit/_ASTransitionContext.h b/Source/_ASTransitionContext.h similarity index 83% rename from AsyncDisplayKit/_ASTransitionContext.h rename to Source/_ASTransitionContext.h index 666aed281d..0b59fe1a84 100644 --- a/AsyncDisplayKit/_ASTransitionContext.h +++ b/Source/_ASTransitionContext.h @@ -12,7 +12,7 @@ #import -#import "ASContextTransitioning.h" +#import @class ASLayout; @class _ASTransitionContext; @@ -44,3 +44,9 @@ completionDelegate:(id<_ASTransitionContextCompletionDelegate>)completionDelegate; @end + +@interface _ASAnimatedTransitionContext : NSObject +@property (nonatomic, strong, readonly) ASDisplayNode *node; +@property (nonatomic, assign, readonly) CGFloat alpha; ++ (instancetype)contextForNode:(ASDisplayNode *)node alpha:(CGFloat)alphaValue; +@end diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/Source/_ASTransitionContext.m similarity index 75% rename from AsyncDisplayKit/_ASTransitionContext.m rename to Source/_ASTransitionContext.m index 4ce9554701..fac52d28e0 100644 --- a/AsyncDisplayKit/_ASTransitionContext.m +++ b/Source/_ASTransitionContext.m @@ -10,9 +10,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "_ASTransitionContext.h" -#import "ASDisplayNode.h" -#import "ASLayout.h" +#import +#import +#import NSString * const ASTransitionContextFromLayoutKey = @"org.asyncdisplaykit.ASTransitionContextFromLayoutKey"; @@ -54,22 +54,12 @@ - (ASSizeRange)constrainedSizeForKey:(NSString *)key - (CGRect)initialFrameForNode:(ASDisplayNode *)node { - for (ASDisplayNode *subnode in [_layoutDelegate currentSubnodesWithTransitionContext:self]) { - if (node == subnode) { - return node.frame; - } - } - return CGRectZero; + return [[self layoutForKey:ASTransitionContextFromLayoutKey] frameForElement:node]; } - (CGRect)finalFrameForNode:(ASDisplayNode *)node { - for (ASLayout *layout in [self layoutForKey:ASTransitionContextToLayoutKey].sublayouts) { - if (layout.layoutElement == node) { - return [layout frame]; - } - } - return CGRectZero; + return [[self layoutForKey:ASTransitionContextToLayoutKey] frameForElement:node]; } - (NSArray *)subnodesForKey:(NSString *)key @@ -97,3 +87,21 @@ - (void)completeTransition:(BOOL)didComplete } @end + + +@interface _ASAnimatedTransitionContext () +@property (nonatomic, strong, readwrite) ASDisplayNode *node; +@property (nonatomic, assign, readwrite) CGFloat alpha; +@end + +@implementation _ASAnimatedTransitionContext + ++ (instancetype)contextForNode:(ASDisplayNode *)node alpha:(CGFloat)alpha +{ + _ASAnimatedTransitionContext *context = [[_ASAnimatedTransitionContext alloc] init]; + context.node = node; + context.alpha = alpha; + return context; +} + +@end diff --git a/AsyncDisplayKit/ASControlNode+tvOS.h b/Source/tvOS/ASControlNode+tvOS.h similarity index 91% rename from AsyncDisplayKit/ASControlNode+tvOS.h rename to Source/tvOS/ASControlNode+tvOS.h index 964fbbc52c..7f56cf5ddd 100644 --- a/AsyncDisplayKit/ASControlNode+tvOS.h +++ b/Source/tvOS/ASControlNode+tvOS.h @@ -11,7 +11,7 @@ // #if TARGET_OS_TV -#import +#import @interface ASControlNode (tvOS) diff --git a/AsyncDisplayKit/ASControlNode+tvOS.m b/Source/tvOS/ASControlNode+tvOS.m similarity index 96% rename from AsyncDisplayKit/ASControlNode+tvOS.m rename to Source/tvOS/ASControlNode+tvOS.m index e4931cd0f9..005b8832ea 100644 --- a/AsyncDisplayKit/ASControlNode+tvOS.m +++ b/Source/tvOS/ASControlNode+tvOS.m @@ -9,8 +9,11 @@ // 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 + #if TARGET_OS_TV -#import "ASControlNode+tvOS.h" +#import @implementation ASControlNode (tvOS) diff --git a/AsyncDisplayKit/ASImageNode+tvOS.h b/Source/tvOS/ASImageNode+tvOS.h similarity index 91% rename from AsyncDisplayKit/ASImageNode+tvOS.h rename to Source/tvOS/ASImageNode+tvOS.h index 1589f3824a..0eaf1c5ee5 100644 --- a/AsyncDisplayKit/ASImageNode+tvOS.h +++ b/Source/tvOS/ASImageNode+tvOS.h @@ -11,7 +11,7 @@ // #if TARGET_OS_TV -#import +#import @interface ASImageNode (tvOS) @end diff --git a/AsyncDisplayKit/ASImageNode+tvOS.m b/Source/tvOS/ASImageNode+tvOS.m similarity index 98% rename from AsyncDisplayKit/ASImageNode+tvOS.m rename to Source/tvOS/ASImageNode+tvOS.m index cc0af5de01..4066c41de2 100644 --- a/AsyncDisplayKit/ASImageNode+tvOS.m +++ b/Source/tvOS/ASImageNode+tvOS.m @@ -10,13 +10,14 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #if TARGET_OS_TV -#import "ASImageNode+tvOS.h" +#import #import #import -#import "ASDisplayNodeExtras.h" +#import @implementation ASImageNode (tvOS) diff --git a/AsyncDisplayKitTests/ASAbsoluteLayoutSpecSnapshotTests.m b/Tests/ASAbsoluteLayoutSpecSnapshotTests.m similarity index 96% rename from AsyncDisplayKitTests/ASAbsoluteLayoutSpecSnapshotTests.m rename to Tests/ASAbsoluteLayoutSpecSnapshotTests.m index 93c5c72e44..f4c238040a 100644 --- a/AsyncDisplayKitTests/ASAbsoluteLayoutSpecSnapshotTests.m +++ b/Tests/ASAbsoluteLayoutSpecSnapshotTests.m @@ -12,8 +12,8 @@ #import "ASLayoutSpecSnapshotTestsHelper.h" -#import "ASAbsoluteLayoutSpec.h" -#import "ASBackgroundLayoutSpec.h" +#import +#import @interface ASAbsoluteLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase @end diff --git a/AsyncDisplayKitTests/ASBackgroundLayoutSpecSnapshotTests.mm b/Tests/ASBackgroundLayoutSpecSnapshotTests.mm similarity index 92% rename from AsyncDisplayKitTests/ASBackgroundLayoutSpecSnapshotTests.mm rename to Tests/ASBackgroundLayoutSpecSnapshotTests.mm index 7775a3a712..31b5522d11 100644 --- a/AsyncDisplayKitTests/ASBackgroundLayoutSpecSnapshotTests.mm +++ b/Tests/ASBackgroundLayoutSpecSnapshotTests.mm @@ -10,8 +10,8 @@ #import "ASLayoutSpecSnapshotTestsHelper.h" -#import "ASBackgroundLayoutSpec.h" -#import "ASCenterLayoutSpec.h" +#import +#import static const ASSizeRange kSize = {{320, 320}, {320, 320}}; diff --git a/AsyncDisplayKitTests/ASBasicImageDownloaderContextTests.m b/Tests/ASBasicImageDownloaderContextTests.m similarity index 97% rename from AsyncDisplayKitTests/ASBasicImageDownloaderContextTests.m rename to Tests/ASBasicImageDownloaderContextTests.m index a50067ab75..8d5338817e 100644 --- a/AsyncDisplayKitTests/ASBasicImageDownloaderContextTests.m +++ b/Tests/ASBasicImageDownloaderContextTests.m @@ -9,7 +9,7 @@ // #import -#import "ASBasicImageDownloaderInternal.h" +#import #import diff --git a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m b/Tests/ASBasicImageDownloaderTests.m similarity index 100% rename from AsyncDisplayKitTests/ASBasicImageDownloaderTests.m rename to Tests/ASBasicImageDownloaderTests.m diff --git a/AsyncDisplayKitTests/ASBatchFetchingTests.m b/Tests/ASBatchFetchingTests.m similarity index 70% rename from AsyncDisplayKitTests/ASBatchFetchingTests.m rename to Tests/ASBatchFetchingTests.m index a4c0b50209..b9fab3f480 100644 --- a/AsyncDisplayKitTests/ASBatchFetchingTests.m +++ b/Tests/ASBatchFetchingTests.m @@ -10,7 +10,8 @@ #import -#import "ASBatchFetching.h" +#import +#import @interface ASBatchFetchingTests : XCTestCase @@ -21,35 +22,35 @@ @implementation ASBatchFetchingTests #define PASSING_RECT CGRectMake(0,0,1,1) #define PASSING_SIZE CGSizeMake(1,1) #define PASSING_POINT CGPointMake(1,1) -#define VERTICAL_RECT(h) CGRectMake(0,0,0,h) +#define VERTICAL_RECT(h) CGRectMake(0,0,1,h) #define VERTICAL_SIZE(h) CGSizeMake(0,h) #define VERTICAL_OFFSET(y) CGPointMake(0,y) -#define HORIZONTAL_RECT(w) CGRectMake(0,0,w,0) +#define HORIZONTAL_RECT(w) CGRectMake(0,0,w,1) #define HORIZONTAL_SIZE(w) CGSizeMake(w,0) #define HORIZONTAL_OFFSET(x) CGPointMake(x,0) - (void)testBatchNullState { ASBatchContext *context = [[ASBatchContext alloc] init]; - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, CGRectZero, CGSizeZero, CGPointZero, 0.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, CGRectZero, CGSizeZero, CGPointZero, 0.0, YES); XCTAssert(shouldFetch == NO, @"Should not fetch in the null state"); } - (void)testBatchAlreadyFetching { ASBatchContext *context = [[ASBatchContext alloc] init]; [context beginBatchFetching]; - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES); XCTAssert(shouldFetch == NO, @"Should not fetch when context is already fetching"); } - (void)testUnsupportedScrollDirections { ASBatchContext *context = [[ASBatchContext alloc] init]; - BOOL fetchRight = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0); + BOOL fetchRight = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES); XCTAssert(fetchRight == YES, @"Should fetch for scrolling right"); - BOOL fetchDown = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0); + BOOL fetchDown = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES); XCTAssert(fetchDown == YES, @"Should fetch for scrolling down"); - BOOL fetchUp = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0); + BOOL fetchUp = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES); XCTAssert(fetchUp == NO, @"Should not fetch for scrolling up"); - BOOL fetchLeft = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0); + BOOL fetchLeft = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES); XCTAssert(fetchLeft == NO, @"Should not fetch for scrolling left"); } @@ -57,7 +58,7 @@ - (void)testVerticalScrollToExactLeading { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // scroll to 1-screen top offset, height is 1 screen, so bottom is 1 screen away from end of content - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 1.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 1.0), 1.0, YES); XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling to exactly 1 leading screen away"); } @@ -65,7 +66,7 @@ - (void)testVerticalScrollToLessThanLeading { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, scroll only 1/2 of one screen - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 0.5), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 0.5), 1.0, YES); XCTAssert(shouldFetch == NO, @"Fetch should not begin when vertically scrolling less than the leading distance away"); } @@ -73,7 +74,7 @@ - (void)testVerticalScrollingPastContentSize { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, top offset to 3-screens, height 1 screen, so its 1 screen past the leading - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 3.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 3.0), 1.0, YES); XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size"); } @@ -81,7 +82,7 @@ - (void)testHorizontalScrollToExactLeading { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // scroll to 1-screen left offset, width is 1 screen, so right is 1 screen away from end of content - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 1.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionVerticalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 1.0), 1.0, YES); XCTAssert(shouldFetch == YES, @"Fetch should begin when horizontally scrolling to exactly 1 leading screen away"); } @@ -89,7 +90,7 @@ - (void)testHorizontalScrollToLessThanLeading { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, scroll only 1/2 of one screen - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 0.5), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 0.5), 1.0, YES); XCTAssert(shouldFetch == NO, @"Fetch should not begin when horizontally scrolling less than the leading distance away"); } @@ -97,7 +98,7 @@ - (void)testHorizontalScrollingPastContentSize { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, left offset to 3-screens, width 1 screen, so its 1 screen past the leading - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 3.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 3.0), 1.0, YES); XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size"); } @@ -105,7 +106,7 @@ - (void)testVerticalScrollingSmallContentSize { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // when the content size is < screen size, the target offset will always be 0 - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 0.5), VERTICAL_OFFSET(0.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 0.5), VERTICAL_OFFSET(0.0), 1.0, YES); XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree"); } @@ -113,7 +114,7 @@ - (void)testHorizontalScrollingSmallContentSize { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // when the content size is < screen size, the target offset will always be 0 - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 0.5), HORIZONTAL_OFFSET(0.0), 1.0); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 0.5), HORIZONTAL_OFFSET(0.0), 1.0, YES); XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree"); } diff --git a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm b/Tests/ASBridgedPropertiesTests.mm similarity index 96% rename from AsyncDisplayKitTests/ASBridgedPropertiesTests.mm rename to Tests/ASBridgedPropertiesTests.mm index f9afa96f2f..b0770a9113 100644 --- a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm +++ b/Tests/ASBridgedPropertiesTests.mm @@ -11,12 +11,12 @@ // #import -#import "ASPendingStateController.h" -#import "ASDisplayNode.h" -#import "ASThread.h" -#import "ASDisplayNodeInternal.h" -#import "_ASPendingState.h" -#import "ASCellNode.h" +#import +#import +#import +#import +#import +#import @interface ASPendingStateController (Testing) - (BOOL)test_isFlushScheduled; diff --git a/AsyncDisplayKitTests/ASCALayerTests.m b/Tests/ASCALayerTests.m similarity index 100% rename from AsyncDisplayKitTests/ASCALayerTests.m rename to Tests/ASCALayerTests.m diff --git a/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm b/Tests/ASCenterLayoutSpecSnapshotTests.mm similarity index 96% rename from AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm rename to Tests/ASCenterLayoutSpecSnapshotTests.mm index 49475e9639..98370e8002 100644 --- a/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm +++ b/Tests/ASCenterLayoutSpecSnapshotTests.mm @@ -10,9 +10,9 @@ #import "ASLayoutSpecSnapshotTestsHelper.h" -#import "ASBackgroundLayoutSpec.h" -#import "ASCenterLayoutSpec.h" -#import "ASStackLayoutSpec.h" +#import +#import +#import static const ASSizeRange kSize = {{100, 120}, {320, 160}}; diff --git a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m b/Tests/ASCollectionViewFlowLayoutInspectorTests.m similarity index 78% rename from AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m rename to Tests/ASCollectionViewFlowLayoutInspectorTests.m index 5c1091bfd6..3dd8d76885 100644 --- a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m +++ b/Tests/ASCollectionViewFlowLayoutInspectorTests.m @@ -11,12 +11,13 @@ #import #import #import +#import "ASXCTExtensions.h" -#import "ASCollectionView.h" -#import "ASCollectionNode.h" -#import "ASCollectionViewFlowLayoutInspector.h" -#import "ASCellNode.h" -#import "ASCollectionView+Undeprecated.h" +#import +#import +#import +#import +#import @interface ASCollectionView (Private) @@ -82,7 +83,7 @@ @interface ASCollectionViewFlowLayoutInspectorTests : XCTestCase /** * Test Delegate for Header Reference Size Implementation */ -@interface HeaderReferenceSizeTestDelegate : NSObject +@interface HeaderReferenceSizeTestDelegate : NSObject @end @@ -98,7 +99,7 @@ - (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollection /** * Test Delegate for Footer Reference Size Implementation */ -@interface FooterReferenceSizeTestDelegate : NSObject +@interface FooterReferenceSizeTestDelegate : NSObject @end @@ -142,10 +143,11 @@ - (void)testThatItReturnsAVerticalConstrainedSizeFromTheHeaderDelegateImplementa collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; - ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0)); - XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation"); + ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0)); + + ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation"); collectionView.asyncDataSource = nil; collectionView.asyncDelegate = nil; @@ -164,10 +166,10 @@ - (void)testThatItReturnsAVerticalConstrainedSizeFromTheFooterDelegateImplementa collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; - ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0)); - XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation"); + ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0)); + ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation"); collectionView.asyncDataSource = nil; collectionView.asyncDelegate = nil; @@ -187,10 +189,10 @@ - (void)testThatItReturnsAVerticalConstrainedSizeFromTheHeaderProperty ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; - ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0)); - XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout"); + ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0)); + ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout"); collectionView.asyncDataSource = nil; collectionView.asyncDelegate = nil; @@ -208,10 +210,10 @@ - (void)testThatItReturnsAVerticalConstrainedSizeFromTheFooterProperty ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; - ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0)); - XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout"); + ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0)); + ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout"); collectionView.asyncDataSource = nil; collectionView.asyncDelegate = nil; @@ -232,10 +234,10 @@ - (void)testThatItReturnsAHorizontalConstrainedSizeFromTheHeaderDelegateImplemen collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; - ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.height)); - XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation"); + ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height)); + ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation"); collectionView.asyncDataSource = nil; collectionView.asyncDelegate = nil; @@ -254,10 +256,10 @@ - (void)testThatItReturnsAHorizontalConstrainedSizeFromTheFooterDelegateImplemen collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; - ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.height)); - XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation"); + ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height)); + ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation"); collectionView.asyncDataSource = nil; collectionView.asyncDelegate = nil; @@ -277,10 +279,10 @@ - (void)testThatItReturnsAHorizontalConstrainedSizeFromTheHeaderProperty ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; - ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.width)); - XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout"); + ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.width)); + ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout"); collectionView.asyncDataSource = nil; collectionView.asyncDelegate = nil; @@ -298,10 +300,10 @@ - (void)testThatItReturnsAHorizontalConstrainedSizeFromTheFooterProperty ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; - ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.height)); - XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout"); + ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height)); + ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout"); collectionView.asyncDataSource = nil; collectionView.asyncDelegate = nil; @@ -315,7 +317,7 @@ - (void)testThatItReturnsZeroSizeWhenNoReferenceSizeIsImplemented ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeZero); XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a zero size"); @@ -334,7 +336,7 @@ - (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheDelegate ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionHeader inSection:0]; XCTAssert(count == 1, @"should have a header supplementary view"); @@ -351,7 +353,7 @@ - (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheLayout ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0]; XCTAssert(count == 1, @"should have a footer supplementary view"); @@ -367,7 +369,7 @@ - (void)testThatItReturnsNoneWhenNoReferenceSizeIsImplemented ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0]; XCTAssert(count == 0, @"should not have a footer supplementary view"); @@ -391,7 +393,7 @@ - (void)testThatItThrowsIfNodeConstrainedSizeIsImplementedOnDataSourceButNotOnDe id delegate = [InspectorTestDataSourceDelegateWithoutNodeConstrainedSize new]; node.delegate = delegate; - ASCollectionViewLayoutInspector *inspector = [[ASCollectionViewLayoutInspector alloc] initWithCollectionView:collectionView]; + ASCollectionViewLayoutInspector *inspector = [[ASCollectionViewLayoutInspector alloc] init]; collectionView.layoutInspector = inspector; XCTAssertThrows([inspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]); diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm similarity index 68% rename from AsyncDisplayKitTests/ASCollectionViewTests.mm rename to Tests/ASCollectionViewTests.mm index 51a4804777..e126edd5fc 100644 --- a/AsyncDisplayKitTests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -9,16 +9,13 @@ // #import -#import "ASCollectionView.h" -#import "ASCollectionDataController.h" -#import "ASCollectionViewFlowLayoutInspector.h" -#import "ASCellNode.h" -#import "ASCollectionNode.h" -#import "ASDisplayNode+Beta.h" -#import "ASSectionContext.h" +#import +#import +#import +#import #import #import -#import "ASCollectionView+Undeprecated.h" +#import @interface ASTextCellNodeWithSetSelectedCounter : ASTextCellNode @@ -58,6 +55,7 @@ @implementation ASTestSectionContext @interface ASCollectionViewTestDelegate : NSObject @property (nonatomic, assign) NSInteger sectionGeneration; +@property (nonatomic, copy) void(^willBeginBatchFetch)(ASBatchContext *); @end @@ -93,16 +91,6 @@ - (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockFo }; } -- (void)collectionView:(ASCollectionView *)collectionView willDisplayNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertNotNil(node.layoutAttributes, @"Expected layout attributes for node in %@ to be non-nil.", NSStringFromSelector(_cmd)); -} - -- (void)collectionView:(ASCollectionView *)collectionView didEndDisplayingNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertNotNil(node.layoutAttributes, @"Expected layout attributes for node in %@ to be non-nil.", NSStringFromSelector(_cmd)); -} - - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return _itemCounts.size(); } @@ -126,7 +114,16 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollection - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - return [[ASCellNode alloc] init]; + return [[ASTextCellNodeWithSetSelectedCounter alloc] init]; +} + +- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context +{ + if (_willBeginBatchFetch != nil) { + _willBeginBatchFetch(context); + } else { + [context cancelBatchFetching]; + } } @end @@ -164,7 +161,7 @@ - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibB @interface ASCollectionView (InternalTesting) -- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController; +- (NSArray *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections; @end @@ -174,6 +171,18 @@ @interface ASCollectionViewTests : XCTestCase @implementation ASCollectionViewTests +- (void)tearDown +{ + // We can't prevent the system from retaining windows, but we can at least clear them out to avoid + // pollution between test cases. + for (UIWindow *window in [UIApplication sharedApplication].windows) { + for (UIView *subview in window.subviews) { + [subview removeFromSuperview]; + } + } + [super tearDown]; +} + - (void)testDataSourceImplementsNecessaryMethods { UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; @@ -207,13 +216,11 @@ - (void)testThatRegisteringASupplementaryNodeStoresItForIntrospection UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; [collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; - XCTAssertEqualObjects([collectionView supplementaryNodeKindsInDataController:nil], @[UICollectionElementKindSectionHeader]); + XCTAssertEqualObjects([collectionView dataController:nil supplementaryNodeKindsInSections:[NSIndexSet indexSetWithIndex:0]], @[UICollectionElementKindSectionHeader]); } - (void)testReloadIfNeeded { - [ASDisplayNode setSuppressesInvalidCollectionUpdateExceptions:NO]; - __block ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; __block ASCollectionViewTestDelegate *del = testController.asyncDelegate; __block ASCollectionNode *cn = testController.collectionNode; @@ -323,26 +330,26 @@ - (void)testSelection - (void)testTuningParametersWithExplicitRangeMode { UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + ASCollectionNode *collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:layout]; ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.1, .trailingBufferScreenfuls = 0.1 }; ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 0.1, .trailingBufferScreenfuls = 0.1 }; ASRangeTuningParameters fullRenderParams = { .leadingBufferScreenfuls = 0.5, .trailingBufferScreenfuls = 0.5 }; ASRangeTuningParameters fullPreloadParams = { .leadingBufferScreenfuls = 1, .trailingBufferScreenfuls = 0.5 }; - [collectionView setTuningParameters:minimumRenderParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay]; - [collectionView setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; - [collectionView setTuningParameters:fullRenderParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay]; - [collectionView setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypePreload]; + [collectionNode setTuningParameters:minimumRenderParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay]; + [collectionNode setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; + [collectionNode setTuningParameters:fullRenderParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay]; + [collectionNode setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypePreload]; XCTAssertTrue(ASRangeTuningParametersEqualToRangeTuningParameters(minimumRenderParams, - [collectionView tuningParametersForRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay])); + [collectionNode tuningParametersForRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay])); XCTAssertTrue(ASRangeTuningParametersEqualToRangeTuningParameters(minimumPreloadParams, - [collectionView tuningParametersForRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload])); + [collectionNode tuningParametersForRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload])); XCTAssertTrue(ASRangeTuningParametersEqualToRangeTuningParameters(fullRenderParams, - [collectionView tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay])); + [collectionNode tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay])); XCTAssertTrue(ASRangeTuningParametersEqualToRangeTuningParameters(fullPreloadParams, - [collectionView tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypePreload])); + [collectionNode tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypePreload])); } - (void)testTuningParameters @@ -373,7 +380,6 @@ - (void)testThatCollectionNodeConformsToExpectedProtocols #pragma mark - Update Validations #define updateValidationTestPrologue \ - [ASDisplayNode setSuppressesInvalidCollectionUpdateExceptions:NO];\ ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil];\ __unused ASCollectionViewTestDelegate *del = testController.asyncDelegate;\ __unused ASCollectionView *cv = testController.collectionView;\ @@ -481,12 +487,24 @@ - (void)testCellNodeLayoutAttributes updateValidationTestPrologue NSSet *nodeBatch1 = [NSSet setWithArray:[cn visibleNodes]]; XCTAssertGreaterThan(nodeBatch1.count, 0); + + NSArray *visibleLayoutAttributesBatch1 = [cv.collectionViewLayout layoutAttributesForElementsInRect:cv.bounds]; + XCTAssertGreaterThan(visibleLayoutAttributesBatch1.count, 0); // Expect all visible nodes get 1 applyLayoutAttributes and have a non-nil value. for (ASTextCellNodeWithSetSelectedCounter *node in nodeBatch1) { XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible nodes."); XCTAssertNotNil(node.layoutAttributes, @"Expected layoutAttributes to be non-nil for visible cell node."); } + + for (UICollectionViewLayoutAttributes *layoutAttributes in visibleLayoutAttributesBatch1) { + if (layoutAttributes.representedElementCategory != UICollectionElementCategorySupplementaryView) { + continue; + } + ASTextCellNodeWithSetSelectedCounter *node = (ASTextCellNodeWithSetSelectedCounter *)[cv supplementaryNodeForElementKind:layoutAttributes.representedElementKind atIndexPath:layoutAttributes.indexPath]; + XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible supplementary nodes."); + XCTAssertNotNil(node.layoutAttributes, @"Expected layoutAttributes to be non-nil for visible supplementary node."); + } // Scroll to next batch of items. NSIndexPath *nextIP = [NSIndexPath indexPathForItem:nodeBatch1.count inSection:0]; @@ -502,6 +520,15 @@ - (void)testCellNodeLayoutAttributes XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible nodes, even after node is removed."); XCTAssertNil(node.layoutAttributes, @"Expected layoutAttributes to be nil for removed cell node."); } + + for (UICollectionViewLayoutAttributes *layoutAttributes in visibleLayoutAttributesBatch1) { + if (layoutAttributes.representedElementCategory != UICollectionElementCategorySupplementaryView) { + continue; + } + ASTextCellNodeWithSetSelectedCounter *node = (ASTextCellNodeWithSetSelectedCounter *)[cv supplementaryNodeForElementKind:layoutAttributes.representedElementKind atIndexPath:layoutAttributes.indexPath]; + XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible supplementary nodes, even after node is removed."); + XCTAssertNil(node.layoutAttributes, @"Expected layoutAttributes to be nil for removed supplementary node."); + } } - (void)testCellNodeIndexPathConsistency @@ -699,7 +726,7 @@ - (void)testThatSectionContextsAreCorrectAfterSectionMove XCTAssertTrue(toSection < originalSection); ASTestSectionContext *movedSectionContext = (ASTestSectionContext *)[cn contextForSection:toSection]; XCTAssertNotNil(movedSectionContext); - // ASCollectionView currently uses ASChangeSetDataController which splits a move operation to a pair of delete and insert ones. + // ASCollectionView currently splits a move operation to a pair of delete and insert ones. // So this movedSectionContext is newly loaded and thus is second generation. XCTAssertEqual(movedSectionContext.sectionGeneration, 2); XCTAssertEqual(movedSectionContext.sectionIndex, toSection); @@ -808,4 +835,294 @@ - (void)testThatNilBatchUpdatesCanBeSubmitted [cn performBatchAnimated:NO updates:nil completion:nil]; } +- (void)testThatDeletedItemsAreMarkedInvisible +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + window.rootViewController = testController; + + __block NSInteger itemCount = 1; + testController.asyncDelegate->_itemCounts = {itemCount}; + [window makeKeyAndVisible]; + [window layoutIfNeeded]; + + ASCollectionNode *cn = testController.collectionNode; + [cn waitUntilAllUpdatesAreCommitted]; + [cn.view layoutIfNeeded]; + ASCellNode *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + XCTAssertTrue(node.visible); + testController.asyncDelegate->_itemCounts = {0}; + [cn deleteItemsAtIndexPaths: @[[NSIndexPath indexPathForItem:0 inSection:0]]]; + [self expectationForPredicate:[NSPredicate predicateWithFormat:@"visible = NO"] evaluatedWithObject:node handler:nil]; + [self waitForExpectationsWithTimeout:3 handler:nil]; +} + +- (void)testThatMultipleBatchFetchesDontHappenUnnecessarily +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + window.rootViewController = testController; + + // Start with 1 item so that our content does not fill bounds. + __block NSInteger itemCount = 1; + testController.asyncDelegate->_itemCounts = {itemCount}; + [window makeKeyAndVisible]; + [window layoutIfNeeded]; + + ASCollectionNode *cn = testController.collectionNode; + [cn waitUntilAllUpdatesAreCommitted]; + XCTAssertGreaterThan(cn.bounds.size.height, cn.view.contentSize.height, @"Expected initial data not to fill collection view area."); + + __block NSUInteger batchFetchCount = 0; + XCTestExpectation *expectation = [self expectationWithDescription:@"Batch fetching completed and then some"]; + __weak ASCollectionViewTestController *weakController = testController; + testController.asyncDelegate.willBeginBatchFetch = ^(ASBatchContext *context) { + + // Ensure only 1 batch fetch happens + batchFetchCount += 1; + if (batchFetchCount > 1) { + XCTFail(@"Too many batch fetches!"); + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + // Up the item count to 1000 so that we're well beyond the + // edge of the collection view and not ready for another batch fetch. + NSMutableArray *indexPaths = [NSMutableArray array]; + for (; itemCount < 1000; itemCount++) { + [indexPaths addObject:[NSIndexPath indexPathForItem:itemCount inSection:0]]; + } + weakController.asyncDelegate->_itemCounts = {itemCount}; + [cn insertItemsAtIndexPaths:indexPaths]; + [context completeBatchFetching:YES]; + + // Let the run loop turn before we consider the test passed. + dispatch_async(dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + }); + }; + [self waitForExpectationsWithTimeout:3 handler:nil]; +} + +- (void)testThatBatchFetchHappensForEmptyCollection +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + window.rootViewController = testController; + + testController.asyncDelegate->_itemCounts = {}; + [window makeKeyAndVisible]; + [window layoutIfNeeded]; + + ASCollectionNode *cn = testController.collectionNode; + [cn waitUntilAllUpdatesAreCommitted]; + + __block NSUInteger batchFetchCount = 0; + XCTestExpectation *e = [self expectationWithDescription:@"Batch fetching completed"]; + testController.asyncDelegate.willBeginBatchFetch = ^(ASBatchContext *context) { + // Ensure only 1 batch fetch happens + batchFetchCount += 1; + if (batchFetchCount > 1) { + XCTFail(@"Too many batch fetches!"); + return; + } + [e fulfill]; + }; + [self waitForExpectationsWithTimeout:3 handler:nil]; +} + +- (void)testThatWeBatchFetchUntilContentRequirementIsMet_Animated +{ + [self _primitiveBatchFetchingFillTestAnimated:YES visible:YES controller:nil]; +} + +- (void)testThatWeBatchFetchUntilContentRequirementIsMet_Nonanimated +{ + [self _primitiveBatchFetchingFillTestAnimated:NO visible:YES controller:nil]; +} + +- (void)testThatWeBatchFetchUntilContentRequirementIsMet_Invisible +{ + [self _primitiveBatchFetchingFillTestAnimated:NO visible:NO controller:nil]; +} + +- (void)testThatWhenWeBecomeVisibleWeWillFetchAdditionalContent +{ + ASCollectionViewTestController *ctrl = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + // Start with 1 empty section + ctrl.asyncDelegate->_itemCounts = {0}; + [self _primitiveBatchFetchingFillTestAnimated:NO visible:NO controller:ctrl]; + XCTAssertGreaterThan([ctrl.collectionNode numberOfItemsInSection:0], 0); + [self _primitiveBatchFetchingFillTestAnimated:NO visible:YES controller:ctrl]; +} + +- (void)_primitiveBatchFetchingFillTestAnimated:(BOOL)animated visible:(BOOL)visible controller:(nullable ASCollectionViewTestController *)testController +{ + if (testController == nil) { + testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + // Start with 1 empty section + testController.asyncDelegate->_itemCounts = {0}; + } + ASCollectionNode *cn = testController.collectionNode; + + UIWindow *window = nil; + UIView *view = nil; + if (visible) { + window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + view = window; + } else { + view = cn.view; + view.frame = [UIScreen mainScreen].bounds; + } + + XCTestExpectation *expectation = [self expectationWithDescription:@"Completed all batch fetches"]; + __weak ASCollectionViewTestController *weakController = testController; + __block NSInteger batchFetchCount = 0; + testController.asyncDelegate.willBeginBatchFetch = ^(ASBatchContext *context) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSInteger fetchIndex = batchFetchCount++; + + NSInteger itemCount = weakController.asyncDelegate->_itemCounts[0]; + weakController.asyncDelegate->_itemCounts[0] = (itemCount + 1); + if (animated) { + [cn insertItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:itemCount inSection:0] ]]; + } else { + [cn performBatchAnimated:NO updates:^{ + [cn insertItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:itemCount inSection:0] ]]; + } completion:nil]; + } + + [context completeBatchFetching:YES]; + + // If no more batch fetches have happened in 1 second, assume we're done. + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + if (fetchIndex == batchFetchCount - 1) { + [expectation fulfill]; + } + }); + }); + }; + window.rootViewController = testController; + + [window makeKeyAndVisible]; + // Trigger the initial reload to start + [view layoutIfNeeded]; + + // Wait for ASDK reload to finish + [cn waitUntilAllUpdatesAreCommitted]; + // Force UIKit to read updated data & range controller to update and account for it + [cn.view layoutIfNeeded]; + [self waitForExpectationsWithTimeout:60 handler:nil]; + + CGFloat contentHeight = cn.view.contentSize.height; + CGFloat requiredContentHeight; + CGFloat itemHeight = [cn.view layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]].size.height; + if (visible) { + requiredContentHeight = CGRectGetMaxY(cn.bounds) + CGRectGetHeight(cn.bounds) * cn.view.leadingScreensForBatching; + } else { + requiredContentHeight = CGRectGetMaxY(cn.bounds); + } + XCTAssertGreaterThan(batchFetchCount, 2); + XCTAssertGreaterThanOrEqual(contentHeight, requiredContentHeight, @"Loaded too little content."); + XCTAssertLessThanOrEqual(contentHeight, requiredContentHeight + 3 * itemHeight, @"Loaded too much content."); +} + +- (void)testInitialRangeBounds +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + ASCollectionNode *cn = testController.collectionNode; + [cn setTuningParameters:{ .leadingBufferScreenfuls = 2, .trailingBufferScreenfuls = 0 } forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; + window.rootViewController = testController; + + [window makeKeyAndVisible]; + // Trigger the initial reload to start + [window layoutIfNeeded]; + + // Wait for ASDK reload to finish + [cn waitUntilAllUpdatesAreCommitted]; + // Force UIKit to read updated data & range controller to update and account for it + [cn.view layoutIfNeeded]; + + CGRect preloadBounds = ({ + CGRect r = CGRectNull; + for (NSInteger s = 0; s < cn.numberOfSections; s++) { + NSInteger c = [cn numberOfItemsInSection:s]; + for (NSInteger i = 0; i < c; i++) { + NSIndexPath *ip = [NSIndexPath indexPathForItem:i inSection:s]; + ASCellNode *node = [cn nodeForItemAtIndexPath:ip]; + if (node.inPreloadState) { + CGRect frame = [cn.view layoutAttributesForItemAtIndexPath:ip].frame; + r = CGRectUnion(r, frame); + } + } + } + r; + }); + CGFloat expectedHeight = cn.bounds.size.height * 3; + XCTAssertEqualWithAccuracy(CGRectGetHeight(preloadBounds), expectedHeight, expectedHeight * 0.1); + XCTAssertEqual([[cn valueForKeyPath:@"rangeController.currentRangeMode"] integerValue], ASLayoutRangeModeMinimum, @"Expected range mode to be minimum before scrolling begins."); +} + +- (void)testTraitCollectionChangesMidUpdate +{ + CGRect screenBounds = [UIScreen mainScreen].bounds; + UIWindow *window = [[UIWindow alloc] initWithFrame:screenBounds]; + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + ASCollectionNode *cn = testController.collectionNode; + window.rootViewController = testController; + + [window makeKeyAndVisible]; + // Trigger the initial reload to start + [window layoutIfNeeded]; + + // The initial reload is async, changing the trait collection here should be "mid-update" + ASPrimitiveTraitCollection traitCollection; + traitCollection.displayScale = cn.primitiveTraitCollection.displayScale + 1; // Just a dummy change + traitCollection.containerSize = screenBounds.size; + cn.primitiveTraitCollection = traitCollection; + + [cn waitUntilAllUpdatesAreCommitted]; + [cn.view layoutIfNeeded]; + + // Assert that the new trait collection is picked up by all cell nodes, including ones that were not allocated but are forced to allocate now + for (NSInteger s = 0; s < cn.numberOfSections; s++) { + NSInteger c = [cn numberOfItemsInSection:s]; + for (NSInteger i = 0; i < c; i++) { + NSIndexPath *ip = [NSIndexPath indexPathForItem:i inSection:s]; + ASCellNode *node = [cn.view nodeForItemAtIndexPath:ip]; + XCTAssertTrue(ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, node.primitiveTraitCollection)); + } + } +} + +/** + * This tests an issue where, since subnode insertions aren't applied until the UIKit layout pass, + * which we trigger during the display phase, subnodes like network image nodes are not preloading + * until this layout pass happens which is too late. + */ +- (void)DISABLED_testThatAutomaticallyManagedSubnodesGetPreloadCallBeforeDisplay +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + window.rootViewController = testController; + ASCollectionNode *cn = testController.collectionNode; + + __block NSInteger itemCount = 100; + testController.asyncDelegate->_itemCounts = {itemCount}; + [window makeKeyAndVisible]; + [window layoutIfNeeded]; + + [cn waitUntilAllUpdatesAreCommitted]; + for (NSInteger i = 0; i < itemCount; i++) { + ASTextCellNodeWithSetSelectedCounter *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; + XCTAssert(node.automaticallyManagesSubnodes, @"Expected test cell node to use automatic subnode management. Can modify the test with a different class if needed."); + ASDisplayNode *subnode = node.textNode; + XCTAssertEqualObjects(NSStringFromASInterfaceState(subnode.interfaceState), NSStringFromASInterfaceState(node.interfaceState), @"Subtree interface state should match cell node interface state for ASM nodes."); + XCTAssert(node.inDisplayState || !node.nodeLoaded, @"Only nodes in the display range should be loaded."); + } + +} + @end diff --git a/AsyncDisplayKitTests/ASControlNodeTests.m b/Tests/ASControlNodeTests.m similarity index 100% rename from AsyncDisplayKitTests/ASControlNodeTests.m rename to Tests/ASControlNodeTests.m diff --git a/AsyncDisplayKitTests/ASDimensionTests.mm b/Tests/ASDimensionTests.mm similarity index 98% rename from AsyncDisplayKitTests/ASDimensionTests.mm rename to Tests/ASDimensionTests.mm index 8858220e19..5c37dead3a 100644 --- a/AsyncDisplayKitTests/ASDimensionTests.mm +++ b/Tests/ASDimensionTests.mm @@ -10,7 +10,7 @@ #import #import "ASXCTExtensions.h" -#import "ASDimension.h" +#import @interface ASDimensionTests : XCTestCase diff --git a/AsyncDisplayKitTests/ASDispatchTests.m b/Tests/ASDispatchTests.m similarity index 96% rename from AsyncDisplayKitTests/ASDispatchTests.m rename to Tests/ASDispatchTests.m index 85498595eb..8b35b5a0d7 100644 --- a/AsyncDisplayKitTests/ASDispatchTests.m +++ b/Tests/ASDispatchTests.m @@ -7,7 +7,7 @@ // #import -#import "ASDispatch.h" +#import @interface ASDispatchTests : XCTestCase diff --git a/AsyncDisplayKitTests/ASDisplayLayerTests.m b/Tests/ASDisplayLayerTests.m similarity index 99% rename from AsyncDisplayKitTests/ASDisplayLayerTests.m rename to Tests/ASDisplayLayerTests.m index adc24f70bf..c0a1cb52fa 100644 --- a/AsyncDisplayKitTests/ASDisplayLayerTests.m +++ b/Tests/ASDisplayLayerTests.m @@ -12,12 +12,10 @@ #import #import - #import -#import "_ASDisplayLayer.h" -#import "_ASAsyncTransactionContainer.h" -#import "ASDisplayNode.h" +#import + #import "ASDisplayNodeTestsHelper.h" static UIImage *bogusImage() { diff --git a/AsyncDisplayKitTests/ASDisplayNodeAppearanceTests.m b/Tests/ASDisplayNodeAppearanceTests.m similarity index 98% rename from AsyncDisplayKitTests/ASDisplayNodeAppearanceTests.m rename to Tests/ASDisplayNodeAppearanceTests.m index 7b629e129c..37fa20488d 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeAppearanceTests.m +++ b/Tests/ASDisplayNodeAppearanceTests.m @@ -14,10 +14,10 @@ #import -#import "_ASDisplayView.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNodeExtras.h" -#import "UIView+ASConvenience.h" +#import +#import +#import +#import // helper functions IMP class_replaceMethodWithBlock(Class class, SEL originalSelector, id block); diff --git a/AsyncDisplayKitTests/ASDisplayNodeExtrasTests.m b/Tests/ASDisplayNodeExtrasTests.m similarity index 100% rename from AsyncDisplayKitTests/ASDisplayNodeExtrasTests.m rename to Tests/ASDisplayNodeExtrasTests.m diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/Tests/ASDisplayNodeImplicitHierarchyTests.m similarity index 98% rename from AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m rename to Tests/ASDisplayNodeImplicitHierarchyTests.m index ea9a7fc382..5d99275b1b 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/Tests/ASDisplayNodeImplicitHierarchyTests.m @@ -12,14 +12,8 @@ #import +#import #import "ASDisplayNodeTestsHelper.h" -#import "ASDisplayNode.h" -#import "ASDisplayNode+Beta.h" -#import "ASDisplayNode+Subclasses.h" - -#import "ASAbsoluteLayoutSpec.h" -#import "ASStackLayoutSpec.h" -#import "ASInsetLayoutSpec.h" @interface ASSpecTestDisplayNode : ASDisplayNode diff --git a/AsyncDisplayKitTests/ASDisplayNodeLayoutTests.mm b/Tests/ASDisplayNodeLayoutTests.mm similarity index 70% rename from AsyncDisplayKitTests/ASDisplayNodeLayoutTests.mm rename to Tests/ASDisplayNodeLayoutTests.mm index e608989bba..435e891cde 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeLayoutTests.mm +++ b/Tests/ASDisplayNodeLayoutTests.mm @@ -11,7 +11,7 @@ #import "ASXCTExtensions.h" #import #import "ASLayoutSpecSnapshotTestsHelper.h" -#import "ASDisplayNode+FrameworkPrivate.h" +#import @interface ASDisplayNodeLayoutTests : XCTestCase @end @@ -121,4 +121,54 @@ - (void)testThatLayoutCreatedWithInvalidSizeCausesException XCTAssertThrows([ASLayout layoutWithLayoutElement:displayNode size:CGSizeMake(INFINITY, INFINITY)]); } +- (void)testThatLayoutElementCreatedInLayoutSpecThatFitsDoNotGetDeallocated +{ + const CGSize kSize = CGSizeMake(300, 300); + + ASDisplayNode *subNode = [[ASDisplayNode alloc] init]; + subNode.automaticallyManagesSubnodes = YES; + subNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + ASTextNode *textNode = [ASTextNode new]; + textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Test Test Test Test Test Test Test Test"]; + ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:textNode]; + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:insetSpec]; + }; + + ASDisplayNode *rootNode = [[ASDisplayNode alloc] init]; + rootNode.automaticallyManagesSubnodes = YES; + rootNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + ASTextNode *textNode = [ASTextNode new]; + textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Test Test Test Test Test"]; + ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:textNode]; + + return [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStretch + children:@[insetSpec, subNode]]; + }; + + rootNode.frame = CGRectMake(0, 0, kSize.width, kSize.height); + [rootNode view]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Execute measure and layout pass"]; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + [rootNode layoutThatFits:ASSizeRangeMake(kSize)]; + + dispatch_async(dispatch_get_main_queue(), ^{ + XCTAssertNoThrow([rootNode.view layoutIfNeeded]); + [expectation fulfill]; + }); + }); + + [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { + if (error) { + XCTFail(@"Expectation failed: %@", error); + } + }]; +} + @end diff --git a/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m b/Tests/ASDisplayNodeSnapshotTests.m similarity index 100% rename from AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m rename to Tests/ASDisplayNodeSnapshotTests.m diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/Tests/ASDisplayNodeTests.mm similarity index 92% rename from AsyncDisplayKitTests/ASDisplayNodeTests.m rename to Tests/ASDisplayNodeTests.mm index 6f9c5c08af..3876aedc60 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/Tests/ASDisplayNodeTests.mm @@ -13,20 +13,23 @@ #import "ASXCTExtensions.h" #import -#import "_ASDisplayLayer.h" -#import "_ASDisplayView.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASDisplayNode+Deprecated.h" +#import +#import +#import +#import +#import +#import #import "ASDisplayNodeTestsHelper.h" -#import "UIView+ASConvenience.h" -#import "ASCellNode.h" -#import "ASImageNode.h" -#import "ASOverlayLayoutSpec.h" -#import "ASInsetLayoutSpec.h" -#import "ASCenterLayoutSpec.h" -#import "ASBackgroundLayoutSpec.h" -#import "ASInternalHelpers.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import // Conveniences for making nodes named a certain way #define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.debugName = @#n @@ -89,7 +92,6 @@ - (void)enterInterfaceState:(ASInterfaceState)interfaceState; @interface ASTestDisplayNode : ASDisplayNode @property (nonatomic, copy) void (^willDeallocBlock)(__unsafe_unretained ASTestDisplayNode *node); @property (nonatomic, copy) CGSize(^calculateSizeBlock)(ASTestDisplayNode *node, CGSize size); -@property (nonatomic) BOOL hasFetchedData; @property (nonatomic, nullable) UIGestureRecognizer *gestureRecognizer; @property (nonatomic, nullable) id idGestureRecognizer; @@ -99,6 +101,7 @@ @interface ASTestDisplayNode : ASDisplayNode @property (nonatomic) BOOL displayRangeStateChangedToYES; @property (nonatomic) BOOL displayRangeStateChangedToNO; +@property (nonatomic) BOOL hasPreloaded; @property (nonatomic) BOOL preloadStateChangedToYES; @property (nonatomic) BOOL preloadStateChangedToNO; @end @@ -113,18 +116,6 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize return _calculateSizeBlock ? _calculateSizeBlock(self, constrainedSize) : CGSizeZero; } -- (void)fetchData -{ - [super fetchData]; - self.hasFetchedData = YES; -} - -- (void)clearFetchedData -{ - [super clearFetchedData]; - self.hasFetchedData = NO; -} - - (void)didEnterDisplayState { [super didEnterDisplayState]; @@ -141,6 +132,7 @@ - (void)didEnterPreloadState { [super didEnterPreloadState]; self.preloadStateChangedToYES = YES; + self.hasPreloaded = YES; } - (void)didExitPreloadState @@ -1106,33 +1098,34 @@ - (void)testSubnodes - (void)testReplaceSubnodeNoView { - [self checkReplaceSubnodeWithView:NO layerBacked:NO]; + [self checkReplaceSubnodeLoaded:NO layerBacked:NO]; } - (void)testReplaceSubnodeNoLayer { - [self checkReplaceSubnodeWithView:NO layerBacked:YES]; + [self checkReplaceSubnodeLoaded:NO layerBacked:YES]; } - (void)testReplaceSubnodeView { - [self checkReplaceSubnodeWithView:YES layerBacked:NO]; + [self checkReplaceSubnodeLoaded:YES layerBacked:NO]; } - (void)testReplaceSubnodeLayer { - [self checkReplaceSubnodeWithView:YES layerBacked:YES]; + [self checkReplaceSubnodeLoaded:YES layerBacked:YES]; } -- (void)checkReplaceSubnodeWithView:(BOOL)loaded layerBacked:(BOOL)isLayerBacked +- (void)checkReplaceSubnodeLoaded:(BOOL)loaded layerBacked:(BOOL)isLayerBacked { DeclareNodeNamed(parent); DeclareNodeNamed(a); DeclareNodeNamed(b); DeclareNodeNamed(c); + DeclareNodeNamed(d); - for (ASDisplayNode *n in @[parent, a, b, c]) { + for (ASDisplayNode *n in @[parent, a, b, c, d]) { n.layerBacked = isLayerBacked; } @@ -1144,7 +1137,6 @@ - (void)checkReplaceSubnodeWithView:(BOOL)loaded layerBacked:(BOOL)isLayerBacked [parent layer]; } - DeclareNodeNamed(d); if (loaded) { XCTAssertFalse(d.nodeLoaded, @"Should not yet be loaded"); } @@ -1738,76 +1730,76 @@ - (void)testBackgroundColorOpaqueRelationshipNoLayer } // Check that nodes who have no cell node (no range controller) -// do get their `fetchData` called, and they do report -// the fetch data interface state. +// do get their `preload` called, and they do report +// the preload interface state. - (void)testInterfaceStateForNonCellNode { ASTestWindow *window = [ASTestWindow new]; ASTestDisplayNode *node = [ASTestDisplayNode new]; XCTAssert(node.interfaceState == ASInterfaceStateNone); - XCTAssert(!node.hasFetchedData); + XCTAssert(!node.hasPreloaded); [window addSubview:node.view]; - XCTAssert(node.hasFetchedData); + XCTAssert(node.hasPreloaded); XCTAssert(node.interfaceState == ASInterfaceStateInHierarchy); [node.view removeFromSuperview]; - // We don't want to call -clearFetchedData on nodes that aren't being managed by a range controller. + // We don't want to call -didExitPreloadState on nodes that aren't being managed by a range controller. // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. // Still, the interfaceState should be None to reflect the current state of the node. // We just don't proactively clear contents or fetched data for this state transition. - XCTAssert(node.hasFetchedData); + XCTAssert(node.hasPreloaded); XCTAssert(node.interfaceState == ASInterfaceStateNone); } // Check that nodes who have no cell node (no range controller) -// do get their `fetchData` called, and they do report -// the fetch data interface state. +// do get their `preload` called, and they do report +// the preload interface state. - (void)testInterfaceStateForCellNode { ASCellNode *cellNode = [ASCellNode new]; ASTestDisplayNode *node = [ASTestDisplayNode new]; XCTAssert(node.interfaceState == ASInterfaceStateNone); - XCTAssert(!node.hasFetchedData); + XCTAssert(!node.hasPreloaded); // Simulate range handler updating cell node. [cellNode addSubnode:node]; [cellNode enterInterfaceState:ASInterfaceStatePreload]; - XCTAssert(node.hasFetchedData); + XCTAssert(node.hasPreloaded); XCTAssert(node.interfaceState == ASInterfaceStatePreload); // If the node goes into a view it should not adopt the `InHierarchy` state. ASTestWindow *window = [ASTestWindow new]; [window addSubview:cellNode.view]; - XCTAssert(node.hasFetchedData); + XCTAssert(node.hasPreloaded); XCTAssert(node.interfaceState == ASInterfaceStateInHierarchy); } -- (void)testSetNeedsDataFetchImmediateState +- (void)testSetNeedsPreloadImmediateState { ASCellNode *cellNode = [ASCellNode new]; ASTestDisplayNode *node = [ASTestDisplayNode new]; [cellNode addSubnode:node]; [cellNode enterInterfaceState:ASInterfaceStatePreload]; - node.hasFetchedData = NO; - [cellNode setNeedsDataFetch]; - XCTAssert(node.hasFetchedData); + node.hasPreloaded = NO; + [cellNode setNeedsPreload]; + XCTAssert(node.hasPreloaded); } -- (void)testFetchDataExitingAndEnteringRange +- (void)testPreloadExitingAndEnteringRange { ASCellNode *cellNode = [ASCellNode new]; ASTestDisplayNode *node = [ASTestDisplayNode new]; [cellNode addSubnode:node]; [cellNode setHierarchyState:ASHierarchyStateRangeManaged]; - // Simulate enter range, fetch data, exit range + // Simulate enter range, preload, exit range [cellNode enterInterfaceState:ASInterfaceStatePreload]; [cellNode exitInterfaceState:ASInterfaceStatePreload]; - node.hasFetchedData = NO; + node.hasPreloaded = NO; [cellNode enterInterfaceState:ASInterfaceStatePreload]; - XCTAssert(node.hasFetchedData); + XCTAssert(node.hasPreloaded); } - (void)testInitWithViewClass @@ -1884,14 +1876,14 @@ - (void)checkNameInDescriptionIsLayerBacked:(BOOL)isLayerBacked ASDisplayNode *node = [[ASDisplayNode alloc] init]; node.layerBacked = isLayerBacked; - XCTAssertFalse([node.description rangeOfString:@"debugName"].location != NSNotFound, @"Shouldn't reference 'debugName' in description"); + XCTAssertFalse([node.description containsString:@"debugName"], @"Shouldn't reference 'debugName' in description"); node.debugName = @"big troll eater name"; - XCTAssertFalse([node.description rangeOfString:node.debugName].location == NSNotFound, @"debugName didn't end up in description"); - XCTAssertFalse([node.description rangeOfString:@"debugName"].location == NSNotFound, @"Shouldn't reference 'debugName' in description"); + XCTAssertTrue([node.description containsString:node.debugName], @"debugName didn't end up in description"); + XCTAssertTrue([node.description containsString:@"debugName"], @"Node description should contain `debugName`."); [node layer]; - XCTAssertFalse([node.description rangeOfString:node.debugName].location == NSNotFound, @"debugName didn't end up in description"); - XCTAssertFalse([node.description rangeOfString:@"debugName"].location == NSNotFound, @"Shouldn't reference 'debugName' in description"); + XCTAssertTrue([node.description containsString:node.debugName], @"debugName didn't end up in description"); + XCTAssertTrue([node.description containsString:@"debugName"], @"Node description should contain `debugName`."); } - (void)testNameInDescriptionLayer @@ -1947,6 +1939,7 @@ - (void)testDidEnterPreloadIsCalledWhenNodesEnterPreloadRange - (void)testDidExitPreloadIsCalledWhenNodesExitPreloadRange { ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init]; + [node setHierarchyState:ASHierarchyStateRangeManaged]; [node recursivelySetInterfaceState:ASInterfaceStatePreload]; [node recursivelySetInterfaceState:ASInterfaceStateDisplay]; @@ -1981,33 +1974,93 @@ - (void)testThatNodeGetsRenderedIfItGoesFromZeroSizeToRealSizeButOnlyOnce } // Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205 -- (void)DISABLED_testThatNodesAreMarkedInvisibleWhenRemovedFromAVisibleRasterizedHierarchy +- (void)testThatRasterizedNodesGetInterfaceStateUpdatesWhenContainerEntersHierarchy { - ASCellNode *supernode = [[ASCellNode alloc] init]; + ASDisplayNode *supernode = [[ASDisplayNode alloc] init]; supernode.shouldRasterizeDescendants = YES; - ASDisplayNode *node = [[ASDisplayNode alloc] init]; + ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + ASSetDebugNames(supernode, subnode); UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - [supernode addSubnode:node]; + [supernode addSubnode:subnode]; [window addSubnode:supernode]; [window makeKeyAndVisible]; - XCTAssertTrue(node.isVisible); - [node removeFromSupernode]; - XCTAssertFalse(node.isVisible); + XCTAssertTrue(ASHierarchyStateIncludesRasterized(subnode.hierarchyState)); + XCTAssertTrue(subnode.isVisible); + [supernode.view removeFromSuperview]; + XCTAssertTrue(ASHierarchyStateIncludesRasterized(subnode.hierarchyState)); + XCTAssertFalse(subnode.isVisible); } // Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205 -- (void)DISABLED_testThatNodesAreMarkedVisibleWhenAddedToARasterizedHierarchyAlreadyOnscreen +- (void)testThatRasterizedNodesGetInterfaceStateUpdatesWhenAddedToContainerThatIsInHierarchy { - ASCellNode *supernode = [[ASCellNode alloc] init]; + ASDisplayNode *supernode = [[ASDisplayNode alloc] init]; supernode.shouldRasterizeDescendants = YES; - ASDisplayNode *node = [[ASDisplayNode alloc] init]; + ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + ASSetDebugNames(supernode, subnode); + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [window addSubnode:supernode]; [window makeKeyAndVisible]; - [supernode addSubnode:node]; - XCTAssertTrue(node.isVisible); - [node removeFromSupernode]; - XCTAssertFalse(node.isVisible); + [supernode addSubnode:subnode]; + XCTAssertTrue(ASHierarchyStateIncludesRasterized(subnode.hierarchyState)); + XCTAssertTrue(subnode.isVisible); + [subnode removeFromSupernode]; + XCTAssertFalse(ASHierarchyStateIncludesRasterized(subnode.hierarchyState)); + XCTAssertFalse(subnode.isVisible); +} + +- (void)testThatLoadedNodeGetsUnloadedIfSubtreeBecomesRasterized +{ + ASDisplayNode *supernode = [[ASDisplayNode alloc] init]; + [supernode view]; + ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + ASSetDebugNames(supernode, subnode); + [supernode addSubnode:subnode]; + XCTAssertTrue(subnode.nodeLoaded); + supernode.shouldRasterizeDescendants = YES; + XCTAssertFalse(subnode.nodeLoaded); +} + +- (void)testThatLoadedNodeGetsUnloadedIfAddedToRasterizedSubtree +{ + ASDisplayNode *supernode = [[ASDisplayNode alloc] init]; + supernode.shouldRasterizeDescendants = YES; + ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + ASSetDebugNames(supernode, subnode); + [subnode view]; + XCTAssertTrue(subnode.nodeLoaded); + [supernode addSubnode:subnode]; + XCTAssertFalse(subnode.nodeLoaded); + XCTAssertTrue(ASHierarchyStateIncludesRasterized(subnode.hierarchyState)); +} + +- (void)testThatClearingRasterizationBitMidwayDownTheTreeWorksRight +{ + ASDisplayNode *topNode = [[ASDisplayNode alloc] init]; + topNode.shouldRasterizeDescendants = YES; + ASDisplayNode *middleNode = [[ASDisplayNode alloc] init]; + middleNode.shouldRasterizeDescendants = YES; + ASDisplayNode *bottomNode = [[ASDisplayNode alloc] init]; + ASSetDebugNames(topNode, middleNode, bottomNode); + [middleNode addSubnode:bottomNode]; + [topNode addSubnode:middleNode]; + XCTAssertTrue(ASHierarchyStateIncludesRasterized(bottomNode.hierarchyState)); + XCTAssertTrue(ASHierarchyStateIncludesRasterized(middleNode.hierarchyState)); + middleNode.shouldRasterizeDescendants = NO; + XCTAssertTrue(ASHierarchyStateIncludesRasterized(bottomNode.hierarchyState)); + XCTAssertTrue(ASHierarchyStateIncludesRasterized(middleNode.hierarchyState)); +} + +- (void)testThatRasterizingWrapperNodesIsNotAllowed +{ + ASDisplayNode *rasterizedSupernode = [[ASDisplayNode alloc] init]; + rasterizedSupernode.shouldRasterizeDescendants = YES; + ASDisplayNode *subnode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{ + return [[UIView alloc] init]; + }]; + ASSetDebugNames(rasterizedSupernode, subnode); + XCTAssertThrows([rasterizedSupernode addSubnode:subnode]); } // Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2011 @@ -2070,8 +2123,8 @@ - (void)testThatSubnodeGetsInterfaceStateSetIfRasterized XCTAssertTrue((node.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload); XCTAssertTrue((subnode.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload); - XCTAssertTrue(node.hasFetchedData); - XCTAssertTrue(subnode.hasFetchedData); + XCTAssertTrue(node.hasPreloaded); + XCTAssertTrue(subnode.hasPreloaded); } // FIXME @@ -2092,11 +2145,13 @@ - (void)testThatItsSafeToAutomeasureANodeMidTransition XCTAssertNoThrow([node.view layoutIfNeeded]); } -- (void)testThatOnDidLoadThrowsIfCalledOnLoaded +- (void)testThatOnDidLoadThrowsIfCalledOnLoadedOffMain { ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init]; [node view]; - XCTAssertThrows([node onDidLoad:^(ASDisplayNode * _Nonnull node) { }]); + [self executeOffThread:^{ + XCTAssertThrows([node onDidLoad:^(ASDisplayNode * _Nonnull node) { }]); + }]; } - (void)testThatOnDidLoadWorks @@ -2226,4 +2281,25 @@ - (void)testThatBackgroundLayoutSpecOrdersSubnodesCorrectly XCTAssertLessThan(underlayIndex, overlayIndex); } +- (void)testThatConvertPointGoesToWindowWhenPassedNil +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 20, 20)]; + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.frame = CGRectMake(10, 10, 10, 10); + [window addSubnode:node]; + CGPoint expectedOrigin = CGPointMake(10, 10); + ASXCTAssertEqualPoints([node convertPoint:node.bounds.origin toNode:nil], expectedOrigin); +} + +- (void)testThatConvertPointGoesToWindowWhenPassedNil_layerBacked +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 20, 20)]; + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.layerBacked = YES; + node.frame = CGRectMake(10, 10, 10, 10); + [window addSubnode:node]; + CGPoint expectedOrigin = CGPointMake(10, 10); + ASXCTAssertEqualPoints([node convertPoint:node.bounds.origin toNode:nil], expectedOrigin); +} + @end diff --git a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.h b/Tests/ASDisplayNodeTestsHelper.h similarity index 87% rename from AsyncDisplayKitTests/ASDisplayNodeTestsHelper.h rename to Tests/ASDisplayNodeTestsHelper.h index 664bf30b77..21a149e4b2 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.h +++ b/Tests/ASDisplayNodeTestsHelper.h @@ -9,13 +9,17 @@ // #import -#import "ASDimension.h" +#import @class ASDisplayNode; typedef BOOL (^as_condition_block_t)(void); +ASDISPLAYNODE_EXTERN_C_BEGIN + BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block); void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size); void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange); + +ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m b/Tests/ASDisplayNodeTestsHelper.m similarity index 95% rename from AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m rename to Tests/ASDisplayNodeTestsHelper.m index d1721cef51..473a17a37c 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m +++ b/Tests/ASDisplayNodeTestsHelper.m @@ -9,8 +9,8 @@ // #import "ASDisplayNodeTestsHelper.h" -#import "ASDisplayNode.h" -#import "ASLayout.h" +#import +#import #import diff --git a/AsyncDisplayKitTests/ASEditableTextNodeTests.m b/Tests/ASEditableTextNodeTests.m similarity index 100% rename from AsyncDisplayKitTests/ASEditableTextNodeTests.m rename to Tests/ASEditableTextNodeTests.m diff --git a/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m b/Tests/ASImageNodeSnapshotTests.m similarity index 97% rename from AsyncDisplayKitTests/ASImageNodeSnapshotTests.m rename to Tests/ASImageNodeSnapshotTests.m index 9ebb36ce30..bb9c730d3d 100644 --- a/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m +++ b/Tests/ASImageNodeSnapshotTests.m @@ -17,6 +17,13 @@ @interface ASImageNodeSnapshotTests : ASSnapshotTestCase @implementation ASImageNodeSnapshotTests +- (void)setUp +{ + [super setUp]; + + self.recordMode = NO; +} + - (UIImage *)testImage { NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"logo-square" diff --git a/AsyncDisplayKitTests/ASInsetLayoutSpecSnapshotTests.mm b/Tests/ASInsetLayoutSpecSnapshotTests.mm similarity index 97% rename from AsyncDisplayKitTests/ASInsetLayoutSpecSnapshotTests.mm rename to Tests/ASInsetLayoutSpecSnapshotTests.mm index f286fa8918..52cfab331c 100644 --- a/AsyncDisplayKitTests/ASInsetLayoutSpecSnapshotTests.mm +++ b/Tests/ASInsetLayoutSpecSnapshotTests.mm @@ -10,8 +10,8 @@ #import "ASLayoutSpecSnapshotTestsHelper.h" -#import "ASBackgroundLayoutSpec.h" -#import "ASInsetLayoutSpec.h" +#import +#import typedef NS_OPTIONS(NSUInteger, ASInsetLayoutSpecTestEdge) { ASInsetLayoutSpecTestEdgeTop = 1 << 0, diff --git a/AsyncDisplayKitTests/ASLayoutElementStyleTests.m b/Tests/ASLayoutElementStyleTests.m similarity index 98% rename from AsyncDisplayKitTests/ASLayoutElementStyleTests.m rename to Tests/ASLayoutElementStyleTests.m index 5a7e7cbe8b..36a8d6a84e 100644 --- a/AsyncDisplayKitTests/ASLayoutElementStyleTests.m +++ b/Tests/ASLayoutElementStyleTests.m @@ -8,8 +8,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #import "ASXCTExtensions.h" -#import "ASLayoutElement.h" +#import #pragma mark - ASLayoutElementStyleTestsDelegate diff --git a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.h b/Tests/ASLayoutSpecSnapshotTestsHelper.h similarity index 97% rename from AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.h rename to Tests/ASLayoutSpecSnapshotTestsHelper.h index c84683a208..bec489a4a5 100644 --- a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.h +++ b/Tests/ASLayoutSpecSnapshotTestsHelper.h @@ -9,7 +9,7 @@ // #import "ASSnapshotTestCase.h" -#import "ASDisplayNode+Subclasses.h" +#import @class ASLayoutSpec; diff --git a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m b/Tests/ASLayoutSpecSnapshotTestsHelper.m similarity index 88% rename from AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m rename to Tests/ASLayoutSpecSnapshotTestsHelper.m index 5d555bdde4..418d2dea12 100644 --- a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m +++ b/Tests/ASLayoutSpecSnapshotTestsHelper.m @@ -10,9 +10,10 @@ #import "ASLayoutSpecSnapshotTestsHelper.h" -#import "ASDisplayNode.h" -#import "ASLayoutSpec.h" -#import "ASLayout.h" +#import +#import +#import +#import @interface ASTestNode : ASDisplayNode @property (strong, nonatomic, nullable) ASLayoutSpec *layoutSpecUnderTest; diff --git a/Tests/ASLayoutSpecTests.m b/Tests/ASLayoutSpecTests.m new file mode 100644 index 0000000000..fc21efb346 --- /dev/null +++ b/Tests/ASLayoutSpecTests.m @@ -0,0 +1,111 @@ +// +// ASLayoutSpecTests.m +// AsyncDisplayKit +// +// Created by Michael Schneider on 1/27/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#import +#import + +#pragma mark - ASDKExtendedLayoutSpec + +/* + * Extend the ASDKExtendedLayoutElement + * It adds a + * - primitive / CGFloat (extendedWidth) + * - struct / ASDimension (extendedDimension) + * - primitive / ASStackLayoutDirection (extendedDirection) + */ +@protocol ASDKExtendedLayoutElement +@property (assign, nonatomic) CGFloat extendedWidth; +@property (assign, nonatomic) ASDimension extendedDimension; +@property (copy, nonatomic) NSString *extendedName; +@end + +/* + * Let the ASLayoutElementStyle conform to the ASDKExtendedLayoutElement protocol and add properties implementation + */ +@interface ASLayoutElementStyle (ASDKExtendedLayoutElement) +@end + +@implementation ASLayoutElementStyle (ASDKExtendedLayoutElement) +ASDK_STYLE_PROP_PRIM(CGFloat, extendedWidth, setExtendedWidth, 0); +ASDK_STYLE_PROP_STR(ASDimension, extendedDimension, setExtendedDimension, ASDimensionMake(ASDimensionUnitAuto, 0)); +ASDK_STYLE_PROP_OBJ(NSString *, extendedName, setExtendedName); +@end + +/* + * As the ASLayoutableStyle conforms to the ASDKExtendedLayoutable protocol now, ASDKExtendedLayoutable properties + * can be accessed in ASDKExtendedLayoutSpec + */ +@interface ASDKExtendedLayoutSpec : ASLayoutSpec +@end + +@implementation ASDKExtendedLayoutSpec + +- (void)doSetSomeStyleValuesToChildren +{ + for (id child in self.children) { + child.style.extendedWidth = 100; + child.style.extendedDimension = ASDimensionMake(100); + child.style.extendedName = @"ASDK"; + } +} + +- (void)doUseSomeStyleValuesFromChildren +{ + for (id child in self.children) { + __unused CGFloat extendedWidth = child.style.extendedWidth; + __unused ASDimension extendedDimension = child.style.extendedDimension; + __unused NSString *extendedName = child.style.extendedName; + } +} + +@end + + +#pragma mark - ASLayoutSpecTests + +@interface ASLayoutSpecTests : XCTestCase + +@end + +@implementation ASLayoutSpecTests + +- (void)testSetPrimitiveToExtendedStyle +{ + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.style.extendedWidth = 100; + XCTAssert(node.style.extendedWidth == 100, @"Primitive value should be set on extended style"); +} + +- (void)testSetStructToExtendedStyle +{ + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.style.extendedDimension = ASDimensionMake(100); + XCTAssertTrue(ASDimensionEqualToDimension(node.style.extendedDimension, ASDimensionMake(100)), @"Struct should be set on extended style"); +} + +- (void)testSetObjectToExtendedStyle +{ + NSString *extendedName = @"ASDK"; + + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.style.extendedName = extendedName; + XCTAssertEqualObjects(node.style.extendedName, extendedName, @"Object should be set on extended style"); +} + + +- (void)testUseOfExtendedStyleProperties +{ + ASDKExtendedLayoutSpec *extendedLayoutSpec = [ASDKExtendedLayoutSpec new]; + extendedLayoutSpec.children = @[[[ASDisplayNode alloc] init], [[ASDisplayNode alloc] init]]; + XCTAssertNoThrow([extendedLayoutSpec doSetSomeStyleValuesToChildren]); + XCTAssertNoThrow([extendedLayoutSpec doUseSomeStyleValuesFromChildren]); +} + +@end diff --git a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m b/Tests/ASMultiplexImageNodeTests.m similarity index 98% rename from AsyncDisplayKitTests/ASMultiplexImageNodeTests.m rename to Tests/ASMultiplexImageNodeTests.m index 4fec68aadf..49a424b5c9 100644 --- a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m +++ b/Tests/ASMultiplexImageNodeTests.m @@ -302,4 +302,10 @@ - (void)testUncachedDownload [mockDelegate verify]; } -@end \ No newline at end of file +- (void)testThatSettingAnImageExternallyWillThrow +{ + ASMultiplexImageNode *multiplexImageNode = [[ASMultiplexImageNode alloc] init]; + XCTAssertThrows(multiplexImageNode.image = [UIImage imageNamed:@""]); +} + +@end diff --git a/AsyncDisplayKitTests/ASMutableAttributedStringBuilderTests.m b/Tests/ASMutableAttributedStringBuilderTests.m similarity index 98% rename from AsyncDisplayKitTests/ASMutableAttributedStringBuilderTests.m rename to Tests/ASMutableAttributedStringBuilderTests.m index e74167cd1a..c638696945 100644 --- a/AsyncDisplayKitTests/ASMutableAttributedStringBuilderTests.m +++ b/Tests/ASMutableAttributedStringBuilderTests.m @@ -10,7 +10,7 @@ #import -#import "ASMutableAttributedStringBuilder.h" +#import @interface ASMutableAttributedStringBuilderTests : XCTestCase diff --git a/AsyncDisplayKitTests/ASNetworkImageNodeTests.m b/Tests/ASNetworkImageNodeTests.m similarity index 83% rename from AsyncDisplayKitTests/ASNetworkImageNodeTests.m rename to Tests/ASNetworkImageNodeTests.m index b7086e6c85..67f9ee347f 100644 --- a/AsyncDisplayKitTests/ASNetworkImageNodeTests.m +++ b/Tests/ASNetworkImageNodeTests.m @@ -9,7 +9,7 @@ #import #import #import -#import "ASDisplayNode+FrameworkPrivate.h" +#import @interface ASNetworkImageNodeTests : XCTestCase @@ -34,7 +34,8 @@ - (void)setUp node = [[ASNetworkImageNode alloc] initWithCache:cache downloader:downloader]; } -- (void)testThatProgressBlockIsSetAndClearedCorrectlyOnVisibility +/// Test is flaky: https://github.com/facebook/AsyncDisplayKit/issues/2898 +- (void)DISABLED_testThatProgressBlockIsSetAndClearedCorrectlyOnVisibility { node.URL = [NSURL URLWithString:@"http://imageA"]; @@ -71,6 +72,17 @@ - (void)testThatProgressBlockIsSetAndClearedCorrectlyOnChangeURL [downloader verifyWithDelay:5]; } +- (void)testThatSettingAnImageWillStayForEnteringAndExitingPreloadState +{ + UIImage *image = [[UIImage alloc] init]; + ASNetworkImageNode *networkImageNode = [[ASNetworkImageNode alloc] init]; + networkImageNode.image = image; + [networkImageNode enterInterfaceState:ASInterfaceStatePreload]; + XCTAssertEqualObjects(image, networkImageNode.image); + [networkImageNode exitInterfaceState:ASInterfaceStatePreload]; + XCTAssertEqualObjects(image, networkImageNode.image); +} + @end @implementation ASTestImageCache diff --git a/AsyncDisplayKitTests/ASOverlayLayoutSpecSnapshotTests.mm b/Tests/ASOverlayLayoutSpecSnapshotTests.mm similarity index 92% rename from AsyncDisplayKitTests/ASOverlayLayoutSpecSnapshotTests.mm rename to Tests/ASOverlayLayoutSpecSnapshotTests.mm index 3e654a9417..a944ce76d4 100644 --- a/AsyncDisplayKitTests/ASOverlayLayoutSpecSnapshotTests.mm +++ b/Tests/ASOverlayLayoutSpecSnapshotTests.mm @@ -10,8 +10,8 @@ #import "ASLayoutSpecSnapshotTestsHelper.h" -#import "ASOverlayLayoutSpec.h" -#import "ASCenterLayoutSpec.h" +#import +#import static const ASSizeRange kSize = {{320, 320}, {320, 320}}; diff --git a/Tests/ASPagerNodeTests.m b/Tests/ASPagerNodeTests.m new file mode 100644 index 0000000000..9117cf372e --- /dev/null +++ b/Tests/ASPagerNodeTests.m @@ -0,0 +1,165 @@ +// +// ASPagerNodeTests.m +// AsyncDisplayKit +// +// Created by Luke Parham on 11/6/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import + +@interface ASPagerNodeTestDataSource : NSObject +@end + +@implementation ASPagerNodeTestDataSource + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + return self; +} + +- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode +{ + return 2; +} + +- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index +{ + return [[ASCellNode alloc] init]; +} + +@end + +@interface ASPagerNodeTestController: UIViewController +@property (nonatomic, strong) ASPagerNodeTestDataSource *testDataSource; +@property (nonatomic, strong) ASPagerNode *pagerNode; +@end + +@implementation ASPagerNodeTestController + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Populate these immediately so that they're not unexpectedly nil during tests. + self.testDataSource = [[ASPagerNodeTestDataSource alloc] init]; + + self.pagerNode = [[ASPagerNode alloc] init]; + self.pagerNode.dataSource = self.testDataSource; + + [self.view addSubnode:self.pagerNode]; + } + return self; +} + +@end + +@interface ASPagerNodeTests : XCTestCase +@property (nonatomic, strong) ASPagerNode *pagerNode; + +@property (nonatomic, strong) ASPagerNodeTestDataSource *testDataSource; +@end + +@implementation ASPagerNodeTests + +- (void)testPagerReturnsIndexOfPages { + ASPagerNodeTestController *testController = [self testController]; + + ASCellNode *cellNode = [testController.pagerNode nodeForPageAtIndex:0]; + + XCTAssertEqual([testController.pagerNode indexOfPageWithNode:cellNode], 0); +} + +- (void)testPagerReturnsNotFoundForCellThatDontExistInPager { + ASPagerNodeTestController *testController = [self testController]; + + ASCellNode *badNode = [[ASCellNode alloc] init]; + + XCTAssertEqual([testController.pagerNode indexOfPageWithNode:badNode], NSNotFound); +} + +- (ASPagerNodeTestController *)testController { + ASPagerNodeTestController *testController = [[ASPagerNodeTestController alloc] initWithNibName:nil bundle:nil]; + UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + [window makeKeyAndVisible]; + window.rootViewController = testController; + + [testController.pagerNode reloadData]; + [testController.pagerNode setNeedsLayout]; + + return testController; +} + +// Disabled due to flakiness https://github.com/facebook/AsyncDisplayKit/issues/2818 +- (void)DISABLED_testThatRootPagerNodeDoesGetTheRightInsetWhilePoppingBack +{ + UICollectionViewCell *cell = nil; + + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; + + ASPagerNodeTestDataSource *dataSource = [[ASPagerNodeTestDataSource alloc] init]; + ASPagerNode *pagerNode = [[ASPagerNode alloc] init]; + pagerNode.dataSource = dataSource; + node.layoutSpecBlock = ^(ASDisplayNode *node, ASSizeRange constrainedSize){ + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:pagerNode]; + }; + ASViewController *vc = [[ASViewController alloc] initWithNode:node]; + UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; + window.rootViewController = nav; + [window makeKeyAndVisible]; + [window layoutIfNeeded]; + + // Wait until view controller is visible + XCTestExpectation *e = [self expectationWithDescription:@"Transition completed"]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [e fulfill]; + }); + [self waitForExpectationsWithTimeout:2 handler:nil]; + + // Test initial values +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + cell = [pagerNode.view cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; +#pragma clang diagnostic pop + XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame)); + XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame)); + XCTAssertEqual(pagerNode.view.contentOffset.y, 0); + XCTAssertEqual(pagerNode.view.contentInset.top, 0); + + e = [self expectationWithDescription:@"Transition completed"]; + // Push another view controller + UIViewController *vc2 = [[UIViewController alloc] init]; + vc2.view.frame = nav.view.bounds; + vc2.view.backgroundColor = [UIColor blueColor]; + [nav pushViewController:vc2 animated:YES]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.505 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [e fulfill]; + }); + [self waitForExpectationsWithTimeout:2 handler:nil]; + + // Pop view controller + e = [self expectationWithDescription:@"Transition completed"]; + [vc2.navigationController popViewControllerAnimated:YES]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.505 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [e fulfill]; + }); + [self waitForExpectationsWithTimeout:2 handler:nil]; + + // Test values again after popping the view controller +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + cell = [pagerNode.view cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; +#pragma clang diagnostic pop + XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame)); + XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame)); + XCTAssertEqual(pagerNode.view.contentOffset.y, 0); + XCTAssertEqual(pagerNode.view.contentInset.top, 0); +} + +@end diff --git a/AsyncDisplayKitTests/ASPerformanceTestContext.h b/Tests/ASPerformanceTestContext.h similarity index 90% rename from AsyncDisplayKitTests/ASPerformanceTestContext.h rename to Tests/ASPerformanceTestContext.h index 2fe0523afa..3d80eb7d74 100644 --- a/AsyncDisplayKitTests/ASPerformanceTestContext.h +++ b/Tests/ASPerformanceTestContext.h @@ -8,6 +8,7 @@ #import #import +#import #define ASXCTAssertRelativePerformanceInRange(test, caseName, min, max) \ _XCTPrimitiveAssertLessThanOrEqual(self, test.results[caseName].relativePerformance, @#caseName, max, @#max);\ @@ -32,7 +33,7 @@ typedef void (^ASTestPerformanceCaseBlock)(NSUInteger i, dispatch_block_t startM /** * The first case you add here will be considered the reference case. */ -- (void)addCaseWithName:(NSString *)caseName block:(__attribute((noescape)) ASTestPerformanceCaseBlock)block; +- (void)addCaseWithName:(NSString *)caseName block:(AS_NOESCAPE ASTestPerformanceCaseBlock)block; @property (nonatomic, copy, readonly) NSDictionary *results; diff --git a/AsyncDisplayKitTests/ASPerformanceTestContext.m b/Tests/ASPerformanceTestContext.m similarity index 93% rename from AsyncDisplayKitTests/ASPerformanceTestContext.m rename to Tests/ASPerformanceTestContext.m index 628999922d..f1c3914e5d 100644 --- a/AsyncDisplayKitTests/ASPerformanceTestContext.m +++ b/Tests/ASPerformanceTestContext.m @@ -8,7 +8,7 @@ #import #import "ASPerformanceTestContext.h" -#import "ASAssert.h" +#import @interface ASPerformanceTestResult () @property (nonatomic) NSTimeInterval timePer1000; @@ -74,7 +74,7 @@ - (BOOL)areAllUserInfosEqual return YES; } -- (void)addCaseWithName:(NSString *)caseName block:(__attribute((noescape)) ASTestPerformanceCaseBlock)block +- (void)addCaseWithName:(NSString *)caseName block:(AS_NOESCAPE ASTestPerformanceCaseBlock)block { ASDisplayNodeAssert(_results[caseName] == nil, @"Already have a case named %@", caseName); ASPerformanceTestResult *result = [[ASPerformanceTestResult alloc] init]; @@ -91,7 +91,7 @@ - (void)addCaseWithName:(NSString *)caseName block:(__attribute((noescape)) ASTe } /// Returns total work time -- (CFTimeInterval)_testPerformanceForCaseWithBlock:(__attribute((noescape)) ASTestPerformanceCaseBlock)block +- (CFTimeInterval)_testPerformanceForCaseWithBlock:(AS_NOESCAPE ASTestPerformanceCaseBlock)block { __block CFTimeInterval time = 0; for (NSInteger i = 0; i < _iterationCount; i++) { diff --git a/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m b/Tests/ASPhotosFrameworkImageRequestTests.m similarity index 97% rename from AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m rename to Tests/ASPhotosFrameworkImageRequestTests.m index ae9b2e4555..954d7bf87b 100644 --- a/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m +++ b/Tests/ASPhotosFrameworkImageRequestTests.m @@ -11,7 +11,7 @@ // #import -#import "ASPhotosFrameworkImageRequest.h" +#import static NSString *const kTestAssetID = @"testAssetID"; diff --git a/AsyncDisplayKitTests/ASRatioLayoutSpecSnapshotTests.mm b/Tests/ASRatioLayoutSpecSnapshotTests.mm similarity index 96% rename from AsyncDisplayKitTests/ASRatioLayoutSpecSnapshotTests.mm rename to Tests/ASRatioLayoutSpecSnapshotTests.mm index ccede39d22..fb50aaa75c 100644 --- a/AsyncDisplayKitTests/ASRatioLayoutSpecSnapshotTests.mm +++ b/Tests/ASRatioLayoutSpecSnapshotTests.mm @@ -10,7 +10,7 @@ #import "ASLayoutSpecSnapshotTestsHelper.h" -#import "ASRatioLayoutSpec.h" +#import static const ASSizeRange kFixedSize = {{0, 0}, {100, 100}}; diff --git a/Tests/ASRectTableTests.m b/Tests/ASRectTableTests.m new file mode 100644 index 0000000000..55c1dbab15 --- /dev/null +++ b/Tests/ASRectTableTests.m @@ -0,0 +1,51 @@ +// +// ASRectTableTests.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/24/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#import "ASRectTable.h" +#import "ASXCTExtensions.h" + +@interface ASRectTableTests : XCTestCase +@end + +@implementation ASRectTableTests + +- (void)testThatItStoresRects +{ + ASRectTable *table = [ASRectTable rectTableForWeakObjectPointers]; + NSObject *key0 = [[NSObject alloc] init]; + NSObject *key1 = [[NSObject alloc] init]; + ASXCTAssertEqualRects([table rectForKey:key0], CGRectNull); + ASXCTAssertEqualRects([table rectForKey:key1], CGRectNull); + CGRect rect0 = CGRectMake(0, 0, 100, 100); + CGRect rect1 = CGRectMake(0, 0, 50, 50); + [table setRect:rect0 forKey:key0]; + [table setRect:rect1 forKey:key1]; + + ASXCTAssertEqualRects([table rectForKey:key0], rect0); + ASXCTAssertEqualRects([table rectForKey:key1], rect1); +} + + +- (void)testCopying +{ + ASRectTable *table = [ASRectTable rectTableForWeakObjectPointers]; + NSObject *key = [[NSObject alloc] init]; + ASXCTAssertEqualRects([table rectForKey:key], CGRectNull); + CGRect rect0 = CGRectMake(0, 0, 100, 100); + CGRect rect1 = CGRectMake(0, 0, 50, 50); + [table setRect:rect0 forKey:key]; + ASRectTable *copy = [table copy]; + [copy setRect:rect1 forKey:key]; + + ASXCTAssertEqualRects([table rectForKey:key], rect0); + ASXCTAssertEqualRects([copy rectForKey:key], rect1); +} + +@end diff --git a/AsyncDisplayKitTests/ASRelativeLayoutSpecSnapshotTests.mm b/Tests/ASRelativeLayoutSpecSnapshotTests.mm similarity index 97% rename from AsyncDisplayKitTests/ASRelativeLayoutSpecSnapshotTests.mm rename to Tests/ASRelativeLayoutSpecSnapshotTests.mm index d507f4b77f..33abb5d443 100644 --- a/AsyncDisplayKitTests/ASRelativeLayoutSpecSnapshotTests.mm +++ b/Tests/ASRelativeLayoutSpecSnapshotTests.mm @@ -10,9 +10,9 @@ #import "ASLayoutSpecSnapshotTestsHelper.h" -#import "ASBackgroundLayoutSpec.h" -#import "ASRelativeLayoutSpec.h" -#import "ASStackLayoutSpec.h" +#import +#import +#import static const ASSizeRange kSize = {{100, 120}, {320, 160}}; @@ -23,14 +23,6 @@ @implementation ASRelativeLayoutSpecSnapshotTests #pragma mark - XCTestCase -- (void)setUp -{ - [super setUp]; - - self.recordMode = NO; -} - - - (void)testWithOptions { [self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionStart]; diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.h b/Tests/ASSnapshotTestCase.h similarity index 74% rename from AsyncDisplayKitTests/ASSnapshotTestCase.h rename to Tests/ASSnapshotTestCase.h index d3f6791d86..501b523a3d 100644 --- a/AsyncDisplayKitTests/ASSnapshotTestCase.h +++ b/Tests/ASSnapshotTestCase.h @@ -19,12 +19,6 @@ NSOrderedSet *ASSnapshotTestCaseDefaultSuffixes(void); { \ [ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:node__]; \ FBSnapshotVerifyLayerWithOptions(node__.layer, identifier__, ASSnapshotTestCaseDefaultSuffixes(), 0) \ - [node__ setShouldRasterizeDescendants:YES]; \ - [ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:node__]; \ - FBSnapshotVerifyLayerWithOptions(node__.layer, identifier__, ASSnapshotTestCaseDefaultSuffixes(), 0) \ - [node__ setShouldRasterizeDescendants:NO]; \ - [ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:node__]; \ - FBSnapshotVerifyLayerWithOptions(node__.layer, identifier__, ASSnapshotTestCaseDefaultSuffixes(), 0) \ } #define ASSnapshotVerifyLayer(layer__, identifier__) \ diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.m b/Tests/ASSnapshotTestCase.m similarity index 70% rename from AsyncDisplayKitTests/ASSnapshotTestCase.m rename to Tests/ASSnapshotTestCase.m index 301ed923fe..81c5b74adf 100644 --- a/AsyncDisplayKitTests/ASSnapshotTestCase.m +++ b/Tests/ASSnapshotTestCase.m @@ -9,28 +9,23 @@ // #import "ASSnapshotTestCase.h" -#import "ASAvailability.h" -#import "ASDisplayNode+Beta.h" -#import "ASDisplayNodeExtras.h" -#import "ASDisplayNode+Subclasses.h" +#import +#import +#import +#import NSOrderedSet *ASSnapshotTestCaseDefaultSuffixes(void) { NSMutableOrderedSet *suffixesSet = [[NSMutableOrderedSet alloc] init]; - // In some rare cases, slightly different rendering may occur on 32 vs 64 bit architectures, - // or on iOS 10 (text rasterization). If the test folders find any image that exactly matches, - // they pass; if an image is not present at all, or it fails, it moves on to check the others. + // In some rare cases, slightly different rendering may occur on iOS 10 (text rasterization). + // If the test folders find any image that exactly matches, they pass; + // if an image is not present at all, or it fails, it moves on to check the others. // This means the order doesn't matter besides reducing logging / performance. - [suffixesSet addObject:@"_32"]; - [suffixesSet addObject:@"_64"]; if (AS_AT_LEAST_IOS10) { [suffixesSet addObject:@"_iOS_10"]; } -#if __LP64__ - return [suffixesSet reversedOrderedSet]; -#else + [suffixesSet addObject:@"_64"]; return [suffixesSet copy]; -#endif } @implementation ASSnapshotTestCase diff --git a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm b/Tests/ASStackLayoutSpecSnapshotTests.mm similarity index 79% rename from AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm rename to Tests/ASStackLayoutSpecSnapshotTests.mm index e82fbe7f12..e39bf2a081 100644 --- a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm +++ b/Tests/ASStackLayoutSpecSnapshotTests.mm @@ -10,26 +10,18 @@ #import "ASLayoutSpecSnapshotTestsHelper.h" -#import "ASStackLayoutSpec.h" -#import "ASStackLayoutSpecUtilities.h" -#import "ASBackgroundLayoutSpec.h" -#import "ASRatioLayoutSpec.h" -#import "ASInsetLayoutSpec.h" +#import +#import +#import +#import +#import +#import @interface ASStackLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase @end @implementation ASStackLayoutSpecSnapshotTests -#pragma mark - XCTestCase - -- (void)setUp -{ - [super setUp]; - - self.recordMode = NO; -} - #pragma mark - Utility methods static NSArray *defaultSubnodes() @@ -57,17 +49,21 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node) node.style.height = ASDimensionMakeWithPoints(size.height); } -- (void)testDefaultStackLayoutElementFlexProperties +static NSArray *defaultTextNodes() { - ASDisplayNode *displayNode = [[ASDisplayNode alloc] init]; - - XCTAssertEqual(displayNode.style.flexShrink, NO); - XCTAssertEqual(displayNode.style.flexGrow, NO); - - const ASDimension unconstrainedDimension = ASDimensionAuto; - const ASDimension flexBasis = displayNode.style.flexBasis; - XCTAssertEqual(flexBasis.unit, unconstrainedDimension.unit); - XCTAssertEqual(flexBasis.value, unconstrainedDimension.value); + ASTextNode *textNode1 = [[ASTextNode alloc] init]; + textNode1.attributedText = [[NSAttributedString alloc] initWithString:@"Hello" + attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:20]}]; + textNode1.backgroundColor = [UIColor redColor]; + textNode1.layerBacked = YES; + + ASTextNode *textNode2 = [[ASTextNode alloc] init]; + textNode2.attributedText = [[NSAttributedString alloc] initWithString:@"Why, hello there! How are you?" + attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:12]}]; + textNode2.backgroundColor = [UIColor blueColor]; + textNode2.layerBacked = YES; + + return @[textNode1, textNode2]; } - (void)testStackLayoutSpecWithJustify:(ASStackLayoutJustifyContent)justify @@ -85,24 +81,6 @@ - (void)testStackLayoutSpecWithJustify:(ASStackLayoutJustifyContent)justify [self testStackLayoutSpecWithStyle:style sizeRange:sizeRange subnodes:subnodes identifier:identifier]; } -- (void)testStackLayoutSpecWithDirection:(ASStackLayoutDirection)direction - itemsHorizontalAlignment:(ASHorizontalAlignment)horizontalAlignment - itemsVerticalAlignment:(ASVerticalAlignment)verticalAlignment - identifier:(NSString *)identifier -{ - NSArray *subnodes = defaultSubnodesWithSameSize({50, 50}, 0); - - ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; - stackLayoutSpec.direction = direction; - stackLayoutSpec.children = subnodes; - stackLayoutSpec.horizontalAlignment = horizontalAlignment; - stackLayoutSpec.verticalAlignment = verticalAlignment; - - CGSize exactSize = CGSizeMake(200, 200); - static ASSizeRange kSize = ASSizeRangeMake(exactSize, exactSize); - [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:subnodes identifier:identifier]; -} - - (void)testStackLayoutSpecWithStyle:(ASStackLayoutSpecStyle)style sizeRange:(ASSizeRange)sizeRange subnodes:(NSArray *)subnodes @@ -123,11 +101,46 @@ - (void)testStackLayoutSpecWithStyle:(ASStackLayoutSpecStyle)style spacing:style.spacing justifyContent:style.justifyContent alignItems:style.alignItems + flexWrap:style.flexWrap + alignContent:style.alignContent children:children]; [self testStackLayoutSpec:stackLayoutSpec sizeRange:sizeRange subnodes:subnodes identifier:identifier]; } +- (void)testStackLayoutSpecWithDirection:(ASStackLayoutDirection)direction + itemsHorizontalAlignment:(ASHorizontalAlignment)horizontalAlignment + itemsVerticalAlignment:(ASVerticalAlignment)verticalAlignment + identifier:(NSString *)identifier +{ + NSArray *subnodes = defaultSubnodesWithSameSize({50, 50}, 0); + + ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; + stackLayoutSpec.direction = direction; + stackLayoutSpec.children = subnodes; + stackLayoutSpec.horizontalAlignment = horizontalAlignment; + stackLayoutSpec.verticalAlignment = verticalAlignment; + + CGSize exactSize = CGSizeMake(200, 200); + static ASSizeRange kSize = ASSizeRangeMake(exactSize, exactSize); + [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:subnodes identifier:identifier]; +} + +- (void)testStackLayoutSpecWithBaselineAlignment:(ASStackLayoutAlignItems)baselineAlignment + identifier:(NSString *)identifier +{ + NSAssert(baselineAlignment == ASStackLayoutAlignItemsBaselineFirst || baselineAlignment == ASStackLayoutAlignItemsBaselineLast, @"Unexpected baseline alignment"); + NSArray *textNodes = defaultTextNodes(); + textNodes[1].style.flexShrink = 1.0; + + ASStackLayoutSpec *stackLayoutSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; + stackLayoutSpec.children = textNodes; + stackLayoutSpec.alignItems = baselineAlignment; + + static ASSizeRange kSize = ASSizeRangeMake(CGSizeMake(150, 0), CGSizeMake(150, CGFLOAT_MAX)); + [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:textNodes identifier:identifier]; +} + - (void)testStackLayoutSpec:(ASStackLayoutSpec *)stackLayoutSpec sizeRange:(ASSizeRange)sizeRange subnodes:(NSArray *)subnodes @@ -142,8 +155,44 @@ - (void)testStackLayoutSpec:(ASStackLayoutSpec *)stackLayoutSpec [self testLayoutSpec:layoutSpec sizeRange:sizeRange subnodes:newSubnodes identifier:identifier]; } +- (void)testStackLayoutSpecWithAlignContent:(ASStackLayoutAlignContent)alignContent + sizeRange:(ASSizeRange)sizeRange + identifier:(NSString *)identifier +{ + ASStackLayoutSpecStyle style = { + .direction = ASStackLayoutDirectionHorizontal, + .flexWrap = ASStackLayoutFlexWrapWrap, + .alignContent = alignContent, + }; + + CGSize subnodeSize = {50, 50}; + NSArray *subnodes = @[ + ASDisplayNodeWithBackgroundColor([UIColor redColor], subnodeSize), + ASDisplayNodeWithBackgroundColor([UIColor yellowColor], subnodeSize), + ASDisplayNodeWithBackgroundColor([UIColor blueColor], subnodeSize), + ASDisplayNodeWithBackgroundColor([UIColor magentaColor], subnodeSize), + ASDisplayNodeWithBackgroundColor([UIColor greenColor], subnodeSize), + ASDisplayNodeWithBackgroundColor([UIColor cyanColor], subnodeSize), + ]; + + [self testStackLayoutSpecWithStyle:style sizeRange:sizeRange subnodes:subnodes identifier:identifier]; +} + #pragma mark - +- (void)testDefaultStackLayoutElementFlexProperties +{ + ASDisplayNode *displayNode = [[ASDisplayNode alloc] init]; + + XCTAssertEqual(displayNode.style.flexShrink, NO); + XCTAssertEqual(displayNode.style.flexGrow, NO); + + const ASDimension unconstrainedDimension = ASDimensionAuto; + const ASDimension flexBasis = displayNode.style.flexBasis; + XCTAssertEqual(flexBasis.unit, unconstrainedDimension.unit); + XCTAssertEqual(flexBasis.value, unconstrainedDimension.value); +} + - (void)testUnderflowBehaviors { // width 300px; height 0-300px @@ -995,11 +1044,31 @@ - (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkT // In this scenario a width of 400 results in a negative violation of 200. // The first and third child components specify a flex shrink factor of 0.25 and will flex by 50. - // The second child component specifies a flex shrink factor of 0.50 and will flex by -57. It will have a width of 43. + // The second child specifies a flex shrink factor of 0.50 and will flex by -57. It will have a width of 43. static ASSizeRange kSize = {{400, 400}, {400, 400}}; [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; } + +- (void)testNegativeViolationAndFlexFactorIsNotRespected +{ + ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; + + NSArray *subnodes = @[ + ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 50}), + ASDisplayNodeWithBackgroundColor([UIColor yellowColor], CGSizeZero), + ]; + + subnodes[0].style.flexShrink = 0; + subnodes[1].style.flexShrink = 1; + + // In this scenario a width of 40 results in a negative violation of 10. + // The first child will not flex. + // The second child specifies a flex shrink factor of 1 but it has a zero size, so the factor won't be respected and it will not shrink. + static ASSizeRange kSize = {{40, 50}, {40, 50}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; +} + - (void)testNestedStackLayoutStretchDoesNotViolateWidth { ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; // Default direction is horizontal @@ -1065,4 +1134,145 @@ - (void)testAlignItemsAndJustifyContentRestrictionsIfHorizontalAndVerticalAlignm XCTAssertEqual(stackLayoutSpec.justifyContent, ASStackLayoutJustifyContentEnd); } +#pragma mark - Baseline alignment tests + +- (void)testBaselineAlignment +{ + [self testStackLayoutSpecWithBaselineAlignment:ASStackLayoutAlignItemsBaselineFirst identifier:@"baselineFirst"]; + [self testStackLayoutSpecWithBaselineAlignment:ASStackLayoutAlignItemsBaselineLast identifier:@"baselineLast"]; +} + +- (void)testNestedBaselineAlignments +{ + NSArray *textNodes = defaultTextNodes(); + + ASDisplayNode *stretchedNode = [[ASDisplayNode alloc] init]; + stretchedNode.layerBacked = YES; + stretchedNode.backgroundColor = [UIColor greenColor]; + stretchedNode.style.alignSelf = ASStackLayoutAlignSelfStretch; + stretchedNode.style.height = ASDimensionMake(100); + + ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStack.children = @[stretchedNode, textNodes[1]]; + verticalStack.style.flexShrink = 1.0; + + ASStackLayoutSpec *horizontalStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; + horizontalStack.children = @[textNodes[0], verticalStack]; + horizontalStack.alignItems = ASStackLayoutAlignItemsBaselineLast; + + NSArray *subnodes = @[textNodes[0], textNodes[1], stretchedNode]; + + static ASSizeRange kSize = ASSizeRangeMake(CGSizeMake(150, 0), CGSizeMake(150, CGFLOAT_MAX)); + [self testStackLayoutSpec:horizontalStack sizeRange:kSize subnodes:subnodes identifier:nil]; +} + +- (void)testBaselineAlignmentWithSpaceBetween +{ + NSArray *textNodes = defaultTextNodes(); + + ASStackLayoutSpec *stackLayoutSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; + stackLayoutSpec.children = textNodes; + stackLayoutSpec.alignItems = ASStackLayoutAlignItemsBaselineFirst; + stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentSpaceBetween; + + static ASSizeRange kSize = ASSizeRangeMake(CGSizeMake(300, 0), CGSizeMake(300, CGFLOAT_MAX)); + [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:textNodes identifier:nil]; +} + +- (void)testBaselineAlignmentWithStretchedItem +{ + NSArray *textNodes = defaultTextNodes(); + + ASDisplayNode *stretchedNode = [[ASDisplayNode alloc] init]; + stretchedNode.layerBacked = YES; + stretchedNode.backgroundColor = [UIColor greenColor]; + stretchedNode.style.alignSelf = ASStackLayoutAlignSelfStretch; + stretchedNode.style.flexShrink = 1.0; + stretchedNode.style.flexGrow = 1.0; + + NSMutableArray *children = [NSMutableArray arrayWithArray:textNodes]; + [children insertObject:stretchedNode atIndex:1]; + + ASStackLayoutSpec *stackLayoutSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; + stackLayoutSpec.children = children; + stackLayoutSpec.alignItems = ASStackLayoutAlignItemsBaselineLast; + stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentSpaceBetween; + + static ASSizeRange kSize = ASSizeRangeMake(CGSizeMake(300, 0), CGSizeMake(300, CGFLOAT_MAX)); + [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:children identifier:nil]; +} + +#pragma mark - Content alignment tests + +- (void)testAlignContentUnderflow +{ + // 3 lines, each line has 2 items, each item has a size of {50, 50} + // width is 110px. It's 10px bigger than the required width of each line (110px vs 100px) to test that items are still correctly collected into lines + static ASSizeRange kSize = {{110, 300}, {110, 300}}; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentStart sizeRange:kSize identifier:@"alignContentStart"]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentCenter sizeRange:kSize identifier:@"alignContentCenter"]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentEnd sizeRange:kSize identifier:@"alignContentEnd"]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentSpaceBetween sizeRange:kSize identifier:@"alignContentSpaceBetween"]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentSpaceAround sizeRange:kSize identifier:@"alignContentSpaceAround"]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentStretch sizeRange:kSize identifier:@"alignContentStretch"]; +} + +- (void)testAlignContentOverflow +{ + // 6 lines, each line has 1 item, each item has a size of {50, 50} + // width is 40px. It's 10px smaller than the width of each item (40px vs 50px) to test that items are still correctly collected into lines + static ASSizeRange kSize = {{40, 260}, {40, 260}}; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentStart sizeRange:kSize identifier:@"alignContentStart"]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentCenter sizeRange:kSize identifier:@"alignContentCenter"]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentEnd sizeRange:kSize identifier:@"alignContentEnd"]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentSpaceBetween sizeRange:kSize identifier:@"alignContentSpaceBetween"]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentSpaceAround sizeRange:kSize identifier:@"alignContentSpaceAround"]; +} + +- (void)testAlignContentWithUnconstrainedCrossSize +{ + // 3 lines, each line has 2 items, each item has a size of {50, 50} + // width is 110px. It's 10px bigger than the required width of each line (110px vs 100px) to test that items are still correctly collected into lines + // height is unconstrained. It causes no cross size violation and the end results are all similar to ASStackLayoutAlignContentStart. + static ASSizeRange kSize = {{110, 0}, {110, CGFLOAT_MAX}}; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentStart sizeRange:kSize identifier:nil]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentCenter sizeRange:kSize identifier:nil]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentEnd sizeRange:kSize identifier:nil]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentSpaceBetween sizeRange:kSize identifier:nil]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentSpaceAround sizeRange:kSize identifier:nil]; + [self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentStretch sizeRange:kSize identifier:nil]; +} + +- (void)testAlignContentStretchAndOtherAlignments +{ + ASStackLayoutSpecStyle style = { + .direction = ASStackLayoutDirectionHorizontal, + .flexWrap = ASStackLayoutFlexWrapWrap, + .alignContent = ASStackLayoutAlignContentStretch, + .alignItems = ASStackLayoutAlignItemsStart, + }; + + CGSize subnodeSize = {50, 50}; + NSArray *subnodes = @[ + // 1st line + ASDisplayNodeWithBackgroundColor([UIColor redColor], subnodeSize), + ASDisplayNodeWithBackgroundColor([UIColor yellowColor], subnodeSize), + // 2nd line + ASDisplayNodeWithBackgroundColor([UIColor blueColor], subnodeSize), + ASDisplayNodeWithBackgroundColor([UIColor magentaColor], subnodeSize), + // 3rd line + ASDisplayNodeWithBackgroundColor([UIColor greenColor], subnodeSize), + ASDisplayNodeWithBackgroundColor([UIColor cyanColor], subnodeSize), + ]; + + subnodes[1].style.alignSelf = ASStackLayoutAlignSelfStart; + subnodes[3].style.alignSelf = ASStackLayoutAlignSelfCenter; + subnodes[5].style.alignSelf = ASStackLayoutAlignSelfEnd; + + // 3 lines, each line has 2 items, each item has a size of {50, 50} + // width is 110px. It's 10px bigger than the required width of each line (110px vs 100px) to test that items are still correctly collected into lines + static ASSizeRange kSize = {{110, 300}, {110, 300}}; + [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; +} + @end diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/Tests/ASTableViewTests.mm similarity index 76% rename from AsyncDisplayKitTests/ASTableViewTests.m rename to Tests/ASTableViewTests.mm index 14a62bb0f5..b66eac6e3b 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/Tests/ASTableViewTests.mm @@ -10,21 +10,21 @@ #import -#import "ASTableView.h" -#import "ASTableViewInternal.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASChangeSetDataController.h" -#import "ASCellNode.h" -#import "ASTableNode.h" -#import "ASTableView+Undeprecated.h" +#import +#import +#import +#import +#import +#import +#import #import #import "ASXCTExtensions.h" +#import #define NumberOfSections 10 -#define NumberOfRowsPerSection 20 #define NumberOfReloadIterations 50 -@interface ASTestDataController : ASChangeSetDataController +@interface ASTestDataController : ASDataController @property (nonatomic) int numberOfAllNodesRelayouts; @end @@ -73,6 +73,8 @@ - (void)dealloc @interface ASTableViewTestDelegate : NSObject @property (nonatomic, copy) void (^willDeallocBlock)(ASTableViewTestDelegate *delegate); +@property (nonatomic) CGFloat headerHeight; +@property (nonatomic) CGFloat footerHeight; @end @implementation ASTableViewTestDelegate @@ -92,6 +94,16 @@ - (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath return nil; } +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section +{ + return _footerHeight; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section +{ + return _headerHeight; +} + - (void)dealloc { if (_willDeallocBlock) { @@ -120,10 +132,21 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize @interface ASTableViewFilledDataSource : NSObject @property (nonatomic) BOOL usesSectionIndex; +@property (nonatomic) NSInteger rowsPerSection; +@property (nonatomic, nullable, copy) ASCellNodeBlock(^nodeBlockForItem)(NSIndexPath *); @end @implementation ASTableViewFilledDataSource +- (instancetype)init +{ + self = [super init]; + if (self != nil) { + _rowsPerSection = 20; + } + return self; +} + - (BOOL)respondsToSelector:(SEL)aSelector { if (aSelector == @selector(sectionIndexTitlesForTableView:) || aSelector == @selector(tableView:sectionForSectionIndexTitle:atIndex:)) { @@ -140,7 +163,7 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return NumberOfRowsPerSection; + return _rowsPerSection; } - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath @@ -153,9 +176,14 @@ - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSInde - (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath { + if (_nodeBlockForItem) { + return _nodeBlockForItem(indexPath); + } + return ^{ ASTestTextCellNode *textCellNode = [ASTestTextCellNode new]; - textCellNode.text = indexPath.description; + textCellNode.text = [NSString stringWithFormat:@"{%d, %d}", (int)indexPath.section, (int)indexPath.row]; + textCellNode.backgroundColor = [UIColor whiteColor]; return textCellNode; }; } @@ -207,7 +235,7 @@ - (void)testDataSourceImplementsNecessaryMethods - (void)testConstrainedSizeForRowAtIndexPath { // Initial width of the table view is non-zero and all nodes are measured with this size. - // Any subsequence size change must trigger a relayout. + // Any subsequent size change must trigger a relayout. // Width and height are swapped so that a later size change will simulate a rotation ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, 100, 400) style:UITableViewStylePlain]; @@ -222,12 +250,13 @@ - (void)testConstrainedSizeForRowAtIndexPath [tableView setNeedsLayout]; [tableView layoutIfNeeded]; + CGFloat separatorHeight = 1.0 / ASScreenScale(); for (int section = 0; section < NumberOfSections; section++) { - for (int row = 0; row < NumberOfRowsPerSection; row++) { + for (int row = 0; row < [tableView numberOfRowsInSection:section]; row++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; CGRect rect = [tableView rectForRowAtIndexPath:indexPath]; XCTAssertEqual(rect.size.width, 100); // specified width should be ignored for table - XCTAssertEqual(rect.size.height, 42); + XCTAssertEqual(rect.size.height, 42 + separatorHeight); } } } @@ -267,13 +296,12 @@ - (NSIndexSet *)randomIndexSet return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(MIN(randA, randB), MAX(randA, randB) - MIN(randA, randB))]; } -- (NSArray *)randomIndexPathsExisting:(BOOL)existing +- (NSArray *)randomIndexPathsExisting:(BOOL)existing rowCount:(NSInteger)rowCount { NSMutableArray *indexPaths = [NSMutableArray array]; [[self randomIndexSet] enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - NSUInteger rowNum = NumberOfRowsPerSection; NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; - for (NSUInteger i = (existing ? 0 : rowNum); i < (existing ? rowNum : rowNum * 2); i++) { + for (NSUInteger i = (existing ? 0 : rowCount); i < (existing ? rowCount : rowCount * 2); i++) { // Maximize evility by sporadically skipping indicies 1/3rd of the time, but only if reloading existing rows if (existing && arc4random_uniform(2) == 0) { continue; @@ -325,7 +353,7 @@ - (void)DISABLED_testReloadData } if (reloadRowsInsteadOfSections) { - NSArray *indexPaths = [self randomIndexPathsExisting:YES]; + NSArray *indexPaths = [self randomIndexPathsExisting:YES rowCount:dataSource.rowsPerSection]; //NSLog(@"reloading rows: %@", indexPaths); [tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:rowAnimation]; } else { @@ -354,7 +382,7 @@ - (void)testRelayoutAllNodesWithNonZeroSizeInitially CGSize tableViewFinalSize = CGSizeMake(100, 500); // Width and height are swapped so that a later size change will simulate a rotation ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width) - style:UITableViewStylePlain]; + style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; @@ -367,36 +395,21 @@ - (void)testRelayoutAllNodesWithNonZeroSizeInitially [self triggerSizeChangeAndAssertRelayoutAllNodesForTableView:tableView newSize:tableViewFinalSize]; } -- (void)testRelayoutAllNodesWithZeroSizeInitially -{ - // Initial width of the table view is 0. The first size change is part of the initial config. - // Any subsequence size change after that must trigger a relayout. - CGSize tableViewFinalSize = CGSizeMake(100, 500); - ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectZero - style:UITableViewStylePlain]; - ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; - - tableView.asyncDelegate = dataSource; - tableView.asyncDataSource = dataSource; - - // Initial configuration - UIView *superview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - [superview addSubview:tableView]; - // Width and height are swapped so that a later size change will simulate a rotation - tableView.frame = CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width); - [tableView layoutIfNeeded]; - - XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 0); - [self triggerSizeChangeAndAssertRelayoutAllNodesForTableView:tableView newSize:tableViewFinalSize]; -} - - (void)testRelayoutVisibleRowsWhenEditingModeIsChanged { CGSize tableViewSize = CGSizeMake(100, 500); ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; - + // Currently this test requires that the text in the cell node fills the + // visible width, so we use the long description for the index path. + dataSource.nodeBlockForItem = ^(NSIndexPath *indexPath) { + return (ASCellNodeBlock)^{ + ASTestTextCellNode *textCellNode = [[ASTestTextCellNode alloc] init]; + textCellNode.text = indexPath.description; + return textCellNode; + }; + }; tableView.asyncDelegate = dataSource; tableView.asyncDataSource = dataSource; @@ -413,7 +426,7 @@ - (void)testRelayoutVisibleRowsWhenEditingModeIsChanged [tableView setEditing:YES]; [tableView endUpdatesAnimated:YES completion:^(BOOL completed) { for (int section = 0; section < NumberOfSections; section++) { - for (int row = 0; row < NumberOfRowsPerSection; row++) { + for (int row = 0; row < dataSource.rowsPerSection; row++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath]; if ([visibleNodes containsObject:node]) { @@ -441,7 +454,7 @@ - (void)testRelayoutVisibleRowsWhenEditingModeIsChanged [tableView setEditing:NO]; [tableView endUpdatesAnimated:YES completion:^(BOOL completed) { for (int section = 0; section < NumberOfSections; section++) { - for (int row = 0; row < NumberOfRowsPerSection; row++) { + for (int row = 0; row < dataSource.rowsPerSection; row++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath]; BOOL visible = [visibleNodes containsObject:node]; @@ -462,7 +475,7 @@ - (void)DISABLED_testRelayoutRowsAfterEditingModeIsChangedAndTheyBecomeVisible { CGSize tableViewSize = CGSizeMake(100, 500); ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) - style:UITableViewStylePlain]; + style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; tableView.asyncDelegate = dataSource; @@ -472,7 +485,7 @@ - (void)DISABLED_testRelayoutRowsAfterEditingModeIsChangedAndTheyBecomeVisible // Cause table view to enter editing mode and then scroll to the bottom. // The last node should be re-measured on main thread with the new (smaller) content view width. - NSIndexPath *lastRowIndexPath = [NSIndexPath indexPathForRow:(NumberOfRowsPerSection - 1) inSection:(NumberOfSections - 1)]; + NSIndexPath *lastRowIndexPath = [NSIndexPath indexPathForRow:(dataSource.rowsPerSection - 1) inSection:(NumberOfSections - 1)]; XCTestExpectation *relayoutExpectation = [self expectationWithDescription:@"relayout"]; [tableView beginUpdates]; [tableView setEditing:YES]; @@ -502,7 +515,7 @@ - (void)testIndexPathForNode [tableView reloadDataWithCompletion:^{ for (NSUInteger i = 0; i < NumberOfSections; i++) { - for (NSUInteger j = 0; j < NumberOfRowsPerSection; j++) { + for (NSUInteger j = 0; j < dataSource.rowsPerSection; j++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:j inSection:i]; ASCellNode *cellNode = [tableView nodeForRowAtIndexPath:indexPath]; NSIndexPath *reportedIndexPath = [tableView indexPathForNode:cellNode]; @@ -517,7 +530,7 @@ - (void)triggerFirstLayoutMeasurementForTableView:(ASTableView *)tableView{ XCTestExpectation *reloadDataExpectation = [self expectationWithDescription:@"reloadData"]; [tableView reloadDataWithCompletion:^{ for (int section = 0; section < NumberOfSections; section++) { - for (int row = 0; row < NumberOfRowsPerSection; row++) { + for (int row = 0; row < [tableView numberOfRowsInSection:section]; row++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath]; XCTAssertEqual(node.numberOfLayoutsOnMainThread, 0); @@ -531,6 +544,9 @@ - (void)triggerFirstLayoutMeasurementForTableView:(ASTableView *)tableView{ XCTFail(@"Expectation failed: %@", error); } }]; + [tableView setNeedsLayout]; + [tableView layoutIfNeeded]; + [tableView waitUntilAllUpdatesAreCommitted]; } - (void)triggerSizeChangeAndAssertRelayoutAllNodesForTableView:(ASTestTableView *)tableView newSize:(CGSize)newSize @@ -548,7 +564,7 @@ - (void)triggerSizeChangeAndAssertRelayoutAllNodesForTableView:(ASTestTableView XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 1); for (int section = 0; section < NumberOfSections; section++) { - for (int row = 0; row < NumberOfRowsPerSection; row++) { + for (int row = 0; row < [tableView numberOfRowsInSection:section]; row++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath]; XCTAssertLessThanOrEqual(node.numberOfLayoutsOnMainThread, 1); @@ -590,15 +606,11 @@ - (void)testThatInitialDataLoadHappensInOneShot [node waitUntilAllUpdatesAreCommitted]; XCTAssertGreaterThan(node.view.numberOfSections, 0); - // Assert that the beginning of the call pattern is correct. - // There is currently noise that comes after that we will allow for this test. + // The first reloadData call helps prevent UITableView from calling it multiple times while ASDataController is working. + // The second reloadData call is the real one. NSArray *expectedSelectors = @[ NSStringFromSelector(@selector(reloadData)), - NSStringFromSelector(@selector(beginUpdates)), - NSStringFromSelector(@selector(insertSections:withRowAnimation:)), - NSStringFromSelector(@selector(insertRowsAtIndexPaths:withRowAnimation:)), - NSStringFromSelector(@selector(endUpdates))]; - NSArray *firstSelectors = [selectors subarrayWithRange:NSMakeRange(0, expectedSelectors.count)]; - XCTAssertEqualObjects(firstSelectors, expectedSelectors); + NSStringFromSelector(@selector(reloadData)) ]; + XCTAssertEqualObjects(selectors, expectedSelectors); [UITableView deswizzleAllInstanceMethods]; } @@ -626,14 +638,8 @@ - (void)testThatReloadDataHappensInOneShot // Assert that the beginning of the call pattern is correct. // There is currently noise that comes after that we will allow for this test. - NSArray *expectedSelectors = @[NSStringFromSelector(@selector(reloadData)), - NSStringFromSelector(@selector(beginUpdates)), - NSStringFromSelector(@selector(deleteSections:withRowAnimation:)), - NSStringFromSelector(@selector(insertSections:withRowAnimation:)), - NSStringFromSelector(@selector(insertRowsAtIndexPaths:withRowAnimation:)), - NSStringFromSelector(@selector(endUpdates))]; - NSArray *firstSelectors = [selectors subarrayWithRange:NSMakeRange(0, expectedSelectors.count)]; - XCTAssertEqualObjects(firstSelectors, expectedSelectors); + NSArray *expectedSelectors = @[ NSStringFromSelector(@selector(reloadData)) ]; + XCTAssertEqualObjects(selectors, expectedSelectors); [UITableView deswizzleAllInstanceMethods]; } @@ -684,6 +690,12 @@ - (void)testSectionIndexHandling // Trigger data load XCTAssertGreaterThan(node.numberOfSections, 0); XCTAssertGreaterThan([node numberOfRowsInSection:0], 0); + + // UITableView's section index view is added only after some rows were inserted to the table. + // All nodes loaded and measured during the initial reloadData used an outdated constrained width (i.e full width: 320). + // So we need to force a new layout pass so that the table will pick up a new constrained size and apply to its node. + [node setNeedsLayout]; + [node.view layoutIfNeeded]; [node waitUntilAllUpdatesAreCommitted]; UITableViewCell *cell = [node.view cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; @@ -716,6 +728,103 @@ - (void)testThatNilBatchUpdatesCanBeSubmitted [node performBatchAnimated:NO updates:nil completion:nil]; } +// https://github.com/facebook/AsyncDisplayKit/issues/2252#issuecomment-263689979 +- (void)testIssue2252 +{ + // Hard-code an iPhone 7 screen. There's something particular about this geometry that causes the issue to repro. + UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 375, 667)]; + + ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStyleGrouped]; + node.frame = window.bounds; + ASTableViewTestDelegate *del = [[ASTableViewTestDelegate alloc] init]; + del.headerHeight = 32; + del.footerHeight = 0.01; + node.delegate = del; + ASTableViewFilledDataSource *ds = [[ASTableViewFilledDataSource alloc] init]; + ds.rowsPerSection = 1; + node.dataSource = ds; + ASViewController *vc = [[ASViewController alloc] initWithNode:node]; + UITabBarController *tabCtrl = [[UITabBarController alloc] init]; + tabCtrl.viewControllers = @[ vc ]; + tabCtrl.tabBar.translucent = NO; + window.rootViewController = tabCtrl; + [window makeKeyAndVisible]; + + [window layoutIfNeeded]; + [node waitUntilAllUpdatesAreCommitted]; + XCTAssertEqual(node.view.numberOfSections, NumberOfSections); + ASXCTAssertEqualRects(CGRectMake(0, 32, 375, 44), [node rectForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]], @"This text requires very specific geometry. The rect for the first row should match up."); + + __unused XCTestExpectation *e = [self expectationWithDescription:@"Did a bunch of rounds of updates."]; + NSInteger totalCount = 20; + __block NSInteger count = 0; + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); + dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC, 0.01 * NSEC_PER_SEC); + dispatch_source_set_event_handler(timer, ^{ + [node performBatchUpdates:^{ + [node reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, NumberOfSections)] withRowAnimation:UITableViewRowAnimationNone]; + } completion:^(BOOL finished) { + if (++count == totalCount) { + dispatch_cancel(timer); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [e fulfill]; + }); + } + }]; + }); + dispatch_resume(timer); + [self waitForExpectationsWithTimeout:60 handler:nil]; +} + +- (void)testThatInvalidUpdateExceptionReasonContainsDataSourceClassName +{ + ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStyleGrouped]; + node.bounds = CGRectMake(0, 0, 100, 100); + ASTableViewFilledDataSource *ds = [[ASTableViewFilledDataSource alloc] init]; + node.dataSource = ds; + + // Force node to load initial data. + [node.view layoutIfNeeded]; + + // Submit an invalid update, ensure exception name matches and that data source is included in the reason. + @try { + [node deleteSections:[NSIndexSet indexSetWithIndex:1000] withRowAnimation:UITableViewRowAnimationNone]; + XCTFail(@"Expected validation to fail."); + } @catch (NSException *e) { + XCTAssertEqual(e.name, ASCollectionInvalidUpdateException); + XCTAssert([e.reason containsString:NSStringFromClass([ds class])], @"Expected validation reason to contain the data source class name. Got:\n%@", e.reason); + } +} + +- (void)testAutomaticallyAdjustingContentOffset +{ + ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain]; + node.view.automaticallyAdjustsContentOffset = YES; + node.bounds = CGRectMake(0, 0, 100, 100); + ASTableViewFilledDataSource *ds = [[ASTableViewFilledDataSource alloc] init]; + node.dataSource = ds; + + [node.view layoutIfNeeded]; + [node waitUntilAllUpdatesAreCommitted]; + CGFloat rowHeight = [node.view rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].size.height; + // Scroll to row (0,1) + 10pt + node.view.contentOffset = CGPointMake(0, rowHeight + 10); + + [node performBatchAnimated:NO updates:^{ + // Delete row 0 from all sections. + // This is silly but it's a consequence of how ASTableViewFilledDataSource is built. + ds.rowsPerSection -= 1; + for (NSInteger i = 0; i < NumberOfSections; i++) { + [node deleteRowsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:i]] withRowAnimation:UITableViewRowAnimationAutomatic]; + } + } completion:nil]; + [node waitUntilAllUpdatesAreCommitted]; + + // Now that row (0,0) is deleted, we should have slid up to be at just 10 + // i.e. we should have subtracted the deleted row height from our content offset. + XCTAssertEqual(node.view.contentOffset.y, 10); +} + @end @implementation UITableView (Testing) diff --git a/AsyncDisplayKitTests/ASTableViewThrashTests.m b/Tests/ASTableViewThrashTests.m similarity index 92% rename from AsyncDisplayKitTests/ASTableViewThrashTests.m rename to Tests/ASTableViewThrashTests.m index 55e64b51db..ac91042b89 100644 --- a/AsyncDisplayKitTests/ASTableViewThrashTests.m +++ b/Tests/ASTableViewThrashTests.m @@ -6,10 +6,10 @@ // Copyright © 2016 Facebook. All rights reserved. // -@import XCTest; +#import #import -#import "ASTableViewInternal.h" -#import "ASTableView+Undeprecated.h" +#import +#import // Set to 1 to use UITableView and see if the issue still exists. @@ -175,6 +175,12 @@ @interface ASThrashTestNode: ASCellNode @implementation ASThrashTestNode +- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize +{ + ASDisplayNodeAssertFalse(isinf(constrainedSize.width)); + return CGSizeMake(constrainedSize.width, 44); +} + @end #endif @@ -188,6 +194,8 @@ @interface ASThrashDataSource: NSObject @property (nonatomic, strong, readonly) UIWindow *window; @property (nonatomic, strong, readonly) TableView *tableView; @property (nonatomic, strong) NSArray *data; +// Only access on main +@property (nonatomic, strong) ASWeakSet *allNodes; @end @@ -199,6 +207,7 @@ - (instancetype)initWithData:(NSArray *)data { _data = [[NSArray alloc] initWithArray:data copyItems:YES]; _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; _tableView = [[TableView alloc] initWithFrame:_window.bounds style:UITableViewStylePlain]; + _allNodes = [[ASWeakSet alloc] init]; [_window addSubview:_tableView]; #if USE_UIKIT_REFERENCE _tableView.dataSource = self; @@ -227,6 +236,17 @@ - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSIntege return self.data[section].headerHeight; } +/// Object passed into predicate is ignored. +- (NSPredicate *)predicateForDeallocatedHierarchy +{ + ASWeakSet *allNodes = self.allNodes; + __weak UIWindow *window = _window; + __weak ASTableView *view = _tableView; + return [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + return window == nil && view == nil && allNodes.isEmpty; + }]; +} + #if USE_UIKIT_REFERENCE - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { @@ -240,13 +260,12 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa #else -- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath { - ASThrashTestItem *item = self.data[indexPath.section].items[indexPath.item]; - return ^{ - ASThrashTestNode *node = [[ASThrashTestNode alloc] init]; - node.item = item; - return node; - }; +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + ASThrashTestNode *node = [[ASThrashTestNode alloc] init]; + node.item = self.data[indexPath.section].items[indexPath.item]; + [self.allNodes addObject:node]; + return node; } #endif @@ -495,11 +514,17 @@ - (void)DISABLED_testRecordedThrashCase { - (void)DISABLED_testThrashingWildly { for (NSInteger i = 0; i < kThrashingIterationCount; i++) { [self setUp]; - ASThrashDataSource *ds = [[ASThrashDataSource alloc] initWithData:[ASThrashTestSection sectionsWithCount:kInitialSectionCount]]; - _update = [[ASThrashUpdate alloc] initWithData:ds.data]; - - [self applyUpdate:_update toDataSource:ds]; - [self verifyDataSource:ds]; + @autoreleasepool { + NSArray *sections = [ASThrashTestSection sectionsWithCount:kInitialSectionCount]; + _update = [[ASThrashUpdate alloc] initWithData:sections]; + ASThrashDataSource *ds = [[ASThrashDataSource alloc] initWithData:sections]; + + [self applyUpdate:_update toDataSource:ds]; + [self verifyDataSource:ds]; + [self expectationForPredicate:[ds predicateForDeallocatedHierarchy] evaluatedWithObject:(id)kCFNull handler:nil]; + } + [self waitForExpectationsWithTimeout:3 handler:nil]; + [self tearDown]; } } @@ -533,7 +558,7 @@ - (void)applyUpdate:(ASThrashUpdate *)update toDataSource:(ASThrashDataSource *) [tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; }]; @try { - [tableView endUpdates]; + [tableView endUpdatesAnimated:NO completion:nil]; #if !USE_UIKIT_REFERENCE [tableView waitUntilAllUpdatesAreCommitted]; #endif diff --git a/AsyncDisplayKitTests/ASTextKitCoreTextAdditionsTests.m b/Tests/ASTextKitCoreTextAdditionsTests.m similarity index 98% rename from AsyncDisplayKitTests/ASTextKitCoreTextAdditionsTests.m rename to Tests/ASTextKitCoreTextAdditionsTests.m index 3421a6c840..95ff3e1fa1 100644 --- a/AsyncDisplayKitTests/ASTextKitCoreTextAdditionsTests.m +++ b/Tests/ASTextKitCoreTextAdditionsTests.m @@ -12,7 +12,7 @@ #import -#import "ASTextKitCoreTextAdditions.h" +#import BOOL floatsCloseEnough(CGFloat float1, CGFloat float2) { CGFloat epsilon = 0.00001; diff --git a/AsyncDisplayKitTests/ASTextKitTests.mm b/Tests/ASTextKitTests.mm similarity index 97% rename from AsyncDisplayKitTests/ASTextKitTests.mm rename to Tests/ASTextKitTests.mm index 60b7765ed9..8e32340b69 100644 --- a/AsyncDisplayKitTests/ASTextKitTests.mm +++ b/Tests/ASTextKitTests.mm @@ -13,10 +13,10 @@ #import -#import "ASTextKitEntityAttribute.h" -#import "ASTextKitAttributes.h" -#import "ASTextKitRenderer.h" -#import "ASTextKitRenderer+Positioning.h" +#import +#import +#import +#import @interface ASTextKitTests : XCTestCase diff --git a/AsyncDisplayKitTests/ASTextKitTruncationTests.mm b/Tests/ASTextKitTruncationTests.mm similarity index 98% rename from AsyncDisplayKitTests/ASTextKitTruncationTests.mm rename to Tests/ASTextKitTruncationTests.mm index a504353af8..75ba4736e3 100644 --- a/AsyncDisplayKitTests/ASTextKitTruncationTests.mm +++ b/Tests/ASTextKitTruncationTests.mm @@ -11,8 +11,8 @@ #import #import -#import "ASTextKitContext.h" -#import "ASTextKitTailTruncater.h" +#import +#import @interface ASTextKitTruncationTests : XCTestCase diff --git a/AsyncDisplayKitTests/ASTextNodePerformanceTests.m b/Tests/ASTextNodePerformanceTests.m similarity index 99% rename from AsyncDisplayKitTests/ASTextNodePerformanceTests.m rename to Tests/ASTextNodePerformanceTests.m index d3a0fa50e8..c6bce2d9fc 100644 --- a/AsyncDisplayKitTests/ASTextNodePerformanceTests.m +++ b/Tests/ASTextNodePerformanceTests.m @@ -10,9 +10,9 @@ #import "ASPerformanceTestContext.h" #import #import -#import "ASinternalHelpers.h" +#import #import "ASXCTExtensions.h" -#import "CoreGraphics+ASConvenience.h" +#import /** * NOTE: This test case is not run during the "test" action. You have to run it manually (click the little diamond.) diff --git a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m b/Tests/ASTextNodeSnapshotTests.m similarity index 98% rename from AsyncDisplayKitTests/ASTextNodeSnapshotTests.m rename to Tests/ASTextNodeSnapshotTests.m index 2a2a1b704d..c0acf7ad4c 100644 --- a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m +++ b/Tests/ASTextNodeSnapshotTests.m @@ -18,6 +18,13 @@ @interface ASTextNodeSnapshotTests : ASSnapshotTestCase @implementation ASTextNodeSnapshotTests +- (void)setUp +{ + [super setUp]; + + self.recordMode = NO; +} + - (void)testTextContainerInset { // trivial test case to ensure ASSnapshotTestCase works diff --git a/AsyncDisplayKitTests/ASTextNodeTests.m b/Tests/ASTextNodeTests.m similarity index 97% rename from AsyncDisplayKitTests/ASTextNodeTests.m rename to Tests/ASTextNodeTests.m index ff1620cd72..2baa932579 100644 --- a/AsyncDisplayKitTests/ASTextNodeTests.m +++ b/Tests/ASTextNodeTests.m @@ -16,7 +16,7 @@ #import #import -#import "CoreGraphics+ASConvenience.h" +#import @interface ASTextNodeTestDelegate : NSObject @@ -171,7 +171,9 @@ - (void)testLinkAttribute ASTextNodeTestDelegate *delegate = [ASTextNodeTestDelegate new]; _textNode.delegate = delegate; - [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))]; + ASLayout *layout = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))]; + _textNode.frame = CGRectMake(0, 0, layout.size.width, layout.size.height); + NSRange returnedLinkRange; NSString *returnedAttributeName; NSString *returnedLinkAttributeValue = [_textNode linkAttributeValueAtPoint:CGPointMake(3, 3) attributeName:&returnedAttributeName range:&returnedLinkRange]; diff --git a/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm b/Tests/ASTextNodeWordKernerTests.mm similarity index 98% rename from AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm rename to Tests/ASTextNodeWordKernerTests.mm index 111714dbf2..91e19b7b25 100644 --- a/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm +++ b/Tests/ASTextNodeWordKernerTests.mm @@ -10,9 +10,9 @@ #import -#import "ASTextKitComponents.h" -#import "ASTextNodeTypes.h" -#import "ASTextNodeWordKerner.h" +#import +#import +#import #pragma mark - Tests diff --git a/AsyncDisplayKitTests/ASUICollectionViewTests.m b/Tests/ASUICollectionViewTests.m similarity index 100% rename from AsyncDisplayKitTests/ASUICollectionViewTests.m rename to Tests/ASUICollectionViewTests.m diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/Tests/ASVideoNodeTests.m similarity index 95% rename from AsyncDisplayKitTests/ASVideoNodeTests.m rename to Tests/ASVideoNodeTests.m index c7a0919e66..c8944bd5ef 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/Tests/ASVideoNodeTests.m @@ -25,12 +25,6 @@ @interface ASVideoNodeTests : XCTestCase } @end -@interface ASNetworkImageNode () { - @public __weak id _delegate; -} -@end - - @interface ASVideoNode () { ASDisplayNode *_playerNode; AVPlayer *_player; @@ -133,7 +127,7 @@ - (void)testPlayerDefaultsToNilWithURL XCTAssertNil(_videoNode.player); } -- (void)testPlayerIsCreatedAsynchronouslyInFetchData +- (void)testPlayerIsCreatedAsynchronouslyInPreload { AVAsset *asset = _firstAsset; @@ -151,7 +145,7 @@ - (void)testPlayerIsCreatedAsynchronouslyInFetchData XCTAssertNotNil(_videoNode.player); } -- (void)testPlayerIsCreatedAsynchronouslyInFetchDataWithURL +- (void)testPlayerIsCreatedAsynchronouslyInPreloadWithURL { AVAsset *asset = [AVAsset assetWithURL:_url]; @@ -387,7 +381,7 @@ - (void)testChangingAssetsChangesPlaceholderImage XCTAssertNotEqual(firstImage, _videoNode.image); } -- (void)testClearingFetchedContentShouldClearAssetData +- (void)testClearingPreloadedContentShouldClearAssetData { AVAsset *asset = _firstAsset; @@ -398,7 +392,7 @@ - (void)testClearingFetchedContentShouldClearAssetData [[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys]; _videoNode.asset = assetMock; - [_videoNode fetchData]; + [_videoNode didEnterPreloadState]; [_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]]; [videoNodeMock verifyWithDelay:1.0f]; @@ -407,10 +401,9 @@ - (void)testClearingFetchedContentShouldClearAssetData XCTAssertNotNil(_videoNode.currentItem); XCTAssertNotNil(_videoNode.image); - [_videoNode clearFetchedData]; + [_videoNode didExitPreloadState]; XCTAssertNil(_videoNode.player); XCTAssertNil(_videoNode.currentItem); - XCTAssertNil(_videoNode.image); } - (void)testDelegateProperlySetForClassHierarchy @@ -420,11 +413,9 @@ - (void)testDelegateProperlySetForClassHierarchy XCTAssertTrue([_videoNode.delegate conformsToProtocol:@protocol(ASVideoNodeDelegate)]); XCTAssertTrue([_videoNode.delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]); XCTAssertTrue([((ASNetworkImageNode*)_videoNode).delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]); - XCTAssertTrue([((ASNetworkImageNode*)_videoNode)->_delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]); XCTAssertEqual(_videoNode.delegate, self); XCTAssertEqual(((ASNetworkImageNode*)_videoNode).delegate, self); - XCTAssertEqual(((ASNetworkImageNode*)_videoNode)->_delegate, self); } @end diff --git a/AsyncDisplayKitTests/ASViewControllerTests.m b/Tests/ASViewControllerTests.m similarity index 100% rename from AsyncDisplayKitTests/ASViewControllerTests.m rename to Tests/ASViewControllerTests.m diff --git a/AsyncDisplayKitTests/ASWeakMapTests.m b/Tests/ASWeakMapTests.m similarity index 97% rename from AsyncDisplayKitTests/ASWeakMapTests.m rename to Tests/ASWeakMapTests.m index 340a6616f1..6934d9207a 100644 --- a/AsyncDisplayKitTests/ASWeakMapTests.m +++ b/Tests/ASWeakMapTests.m @@ -11,7 +11,7 @@ // #import -#import "ASWeakMap.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKitTests/ASWeakSetTests.m b/Tests/ASWeakSetTests.m similarity index 98% rename from AsyncDisplayKitTests/ASWeakSetTests.m rename to Tests/ASWeakSetTests.m index 66063773c9..b0ef35b05e 100644 --- a/AsyncDisplayKitTests/ASWeakSetTests.m +++ b/Tests/ASWeakSetTests.m @@ -11,7 +11,7 @@ // #import -#import "ASWeakSet.h" +#import @interface ASWeakSetTests : XCTestCase diff --git a/AsyncDisplayKitTests/ASWrapperSpecSnapshotTests.mm b/Tests/ASWrapperSpecSnapshotTests.mm similarity index 95% rename from AsyncDisplayKitTests/ASWrapperSpecSnapshotTests.mm rename to Tests/ASWrapperSpecSnapshotTests.mm index d28b8b0fe2..21619348af 100644 --- a/AsyncDisplayKitTests/ASWrapperSpecSnapshotTests.mm +++ b/Tests/ASWrapperSpecSnapshotTests.mm @@ -10,20 +10,13 @@ #import "ASLayoutSpecSnapshotTestsHelper.h" -#import "ASBackgroundLayoutSpec.h" +#import @interface ASWrapperSpecSnapshotTests : ASLayoutSpecSnapshotTestCase @end @implementation ASWrapperSpecSnapshotTests -- (void)setUp -{ - [super setUp]; - - self.recordMode = NO; -} - - (void)testWrapperSpecWithOneElementShouldSizeToElement { ASDisplayNode *child = ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 50}); diff --git a/Tests/ASXCTExtensions.h b/Tests/ASXCTExtensions.h new file mode 100644 index 0000000000..574b7a7175 --- /dev/null +++ b/Tests/ASXCTExtensions.h @@ -0,0 +1,36 @@ +/** + * XCTest extensions for CGGeometry. + * + * Prefer these to XCTAssert(CGRectEqualToRect(...)) because you get output + * that tells you what went wrong. + * Could use NSValue, but using strings makes the description messages shorter. + */ + +#import + +#define ASXCTAssertEqualSizes(s0, s1, ...) \ + _XCTPrimitiveAssertEqualObjects(self, NSStringFromCGSize(s0), @#s0, NSStringFromCGSize(s1), @#s1, __VA_ARGS__) + +#define ASXCTAssertNotEqualSizes(s0, s1, ...) \ + _XCTPrimitiveAssertNotEqualObjects(self, NSStringFromCGSize(s0), @#s0, NSStringFromCGSize(s1), @#s1, __VA_ARGS__) + +#define ASXCTAssertEqualPoints(p0, p1, ...) \ + _XCTPrimitiveAssertEqualObjects(self, NSStringFromCGPoint(p0), @#p0, NSStringFromCGPoint(p1), @#p1, __VA_ARGS__) + +#define ASXCTAssertNotEqualPoints(p0, p1, ...) \ + _XCTPrimitiveAssertNotEqualObjects(self, NSStringFromCGPoint(p0), @#p0, NSStringFromCGPoint(p1), @#p1, __VA_ARGS__) + +#define ASXCTAssertEqualRects(r0, r1, ...) \ + _XCTPrimitiveAssertEqualObjects(self, NSStringFromCGRect(r0), @#r0, NSStringFromCGRect(r1), @#r1, __VA_ARGS__) + +#define ASXCTAssertNotEqualRects(r0, r1, ...) \ + _XCTPrimitiveAssertNotEqualObjects(self, NSStringFromCGRect(r0), @#r0, NSStringFromCGRect(r1), @#r1, __VA_ARGS__) + +#define ASXCTAssertEqualDimensions(r0, r1, ...) \ + _XCTPrimitiveAssertEqualObjects(self, NSStringFromASDimension(r0), @#r0, NSStringFromASDimension(r1), @#r1, __VA_ARGS__) + +#define ASXCTAssertNotEqualDimensions(r0, r1, ...) \ + _XCTPrimitiveAssertNotEqualObjects(self, NSStringFromASDimension(r0), @#r0, NSStringFromASDimension(r1), @#r1, __VA_ARGS__) + +#define ASXCTAssertEqualSizeRanges(r0, r1, ...) \ + _XCTPrimitiveAssertEqualObjects(self, NSStringFromASSizeRange(r0), @#r0, NSStringFromASSizeRange(r1), @#r1, __VA_ARGS__) diff --git a/AsyncDisplayKitTests/ArrayDiffingTests.m b/Tests/ArrayDiffingTests.m similarity index 98% rename from AsyncDisplayKitTests/ArrayDiffingTests.m rename to Tests/ArrayDiffingTests.m index efe1e98444..a7d869bbb3 100644 --- a/AsyncDisplayKitTests/ArrayDiffingTests.m +++ b/Tests/ArrayDiffingTests.m @@ -12,7 +12,7 @@ #import -#import "NSArray+Diffing.h" +#import @interface NSArray (ArrayDiffingTests) - (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL (^)(id lhs, id rhs))comparison; diff --git a/AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist b/Tests/AsyncDisplayKitTests-Info.plist similarity index 100% rename from AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist rename to Tests/AsyncDisplayKitTests-Info.plist diff --git a/AsyncDisplayKitTests/AsyncDisplayKitTests-Prefix.pch b/Tests/AsyncDisplayKitTests-Prefix.pch similarity index 100% rename from AsyncDisplayKitTests/AsyncDisplayKitTests-Prefix.pch rename to Tests/AsyncDisplayKitTests-Prefix.pch diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testChildrenMeasuredWithAutoMaxSize@2x.png b/Tests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testChildrenMeasuredWithAutoMaxSize@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testChildrenMeasuredWithAutoMaxSize@2x.png rename to Tests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testChildrenMeasuredWithAutoMaxSize@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_overflowChildren@2x.png b/Tests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_overflowChildren@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_overflowChildren@2x.png rename to Tests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_overflowChildren@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_underflowChildren@2x.png b/Tests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_underflowChildren@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_underflowChildren@2x.png rename to Tests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_underflowChildren@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_wrappedChildren@2x.png b/Tests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_wrappedChildren@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_wrappedChildren@2x.png rename to Tests/ReferenceImages_64/ASAbsoluteLayoutSpecSnapshotTests/testSizingBehaviour_wrappedChildren@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASBackgroundLayoutSpecSnapshotTests/testBackground@2x.png b/Tests/ReferenceImages_64/ASBackgroundLayoutSpecSnapshotTests/testBackground@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASBackgroundLayoutSpecSnapshotTests/testBackground@2x.png rename to Tests/ReferenceImages_64/ASBackgroundLayoutSpecSnapshotTests/testBackground@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotCentering@2x.png b/Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotCentering@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotCentering@2x.png rename to Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotCentering@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png b/Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png rename to Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringX@2x.png b/Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringX@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringX@2x.png rename to Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringX@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringXCenteringY@2x.png b/Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringXCenteringY@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringXCenteringY@2x.png rename to Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringXCenteringY@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringY@2x.png b/Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringY@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringY@2x.png rename to Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithOptions_CenteringY@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png b/Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png rename to Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumX@2x.png b/Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumX@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumX@2x.png rename to Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumX@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumXSizingMinimumY@2x.png b/Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumXSizingMinimumY@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumXSizingMinimumY@2x.png rename to Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumXSizingMinimumY@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumY@2x.png b/Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumY@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumY@2x.png rename to Tests/ReferenceImages_64/ASCenterLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumY@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASDisplayNodeSnapshotTests/testBasicHierarchySnapshotTesting@2x.png b/Tests/ReferenceImages_64/ASDisplayNodeSnapshotTests/testBasicHierarchySnapshotTesting@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASDisplayNodeSnapshotTests/testBasicHierarchySnapshotTesting@2x.png rename to Tests/ReferenceImages_64/ASDisplayNodeSnapshotTests/testBasicHierarchySnapshotTesting@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASImageNodeSnapshotTests/testForcedScaling_first@2x.png b/Tests/ReferenceImages_64/ASImageNodeSnapshotTests/testForcedScaling_first@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASImageNodeSnapshotTests/testForcedScaling_first@2x.png rename to Tests/ReferenceImages_64/ASImageNodeSnapshotTests/testForcedScaling_first@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASImageNodeSnapshotTests/testForcedScaling_second@2x.png b/Tests/ReferenceImages_64/ASImageNodeSnapshotTests/testForcedScaling_second@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASImageNodeSnapshotTests/testForcedScaling_second@2x.png rename to Tests/ReferenceImages_64/ASImageNodeSnapshotTests/testForcedScaling_second@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASImageNodeSnapshotTests/testRenderLogoSquare@2x.png b/Tests/ReferenceImages_64/ASImageNodeSnapshotTests/testRenderLogoSquare@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_32/ASImageNodeSnapshotTests/testRenderLogoSquare@2x.png rename to Tests/ReferenceImages_64/ASImageNodeSnapshotTests/testRenderLogoSquare@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png b/Tests/ReferenceImages_64/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png rename to Tests/ReferenceImages_64/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASImageNodeSnapshotTests/testTintColorBlock@2x.png b/Tests/ReferenceImages_64/ASImageNodeSnapshotTests/testTintColorBlock@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASImageNodeSnapshotTests/testTintColorBlock@2x.png rename to Tests/ReferenceImages_64/ASImageNodeSnapshotTests/testTintColorBlock@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-10-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-10-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-10-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_10-inf-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-10-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-10-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-10-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithFixedSize_inf-inf-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-0@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-0@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-0@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-0@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-0-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-0@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-0@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-0@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-0@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-0-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-0@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-0@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-0@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-0@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-0-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-0@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-0@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-0@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-0@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_0-inf-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-0@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-0@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-0@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-0@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-0-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-0@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-0@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-0@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-0@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-0-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-0@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-0@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-0@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-0@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-0-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-0@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-0@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-0@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-0@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithInfinityAndZeroInsetValue_inf-inf-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-10-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-10-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-10-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_10-inf-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-10-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-10-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-10-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-10@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-10@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-10@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-10@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-inf@2x.png b/Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-inf@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-inf@2x.png rename to Tests/ReferenceImages_64/ASInsetLayoutSpecSnapshotTests/testInsetsWithVariableSize_inf-inf-inf-inf@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASOverlayLayoutSpecSnapshotTests/testOverlay@2x.png b/Tests/ReferenceImages_64/ASOverlayLayoutSpecSnapshotTests/testOverlay@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASOverlayLayoutSpecSnapshotTests/testOverlay@2x.png rename to Tests/ReferenceImages_64/ASOverlayLayoutSpecSnapshotTests/testOverlay@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_DoubleRatio@2x.png b/Tests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_DoubleRatio@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_DoubleRatio@2x.png rename to Tests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_DoubleRatio@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_HalfRatio@2x.png b/Tests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_HalfRatio@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_HalfRatio@2x.png rename to Tests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_HalfRatio@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_SevenTimesRatio@2x.png b/Tests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_SevenTimesRatio@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_SevenTimesRatio@2x.png rename to Tests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_SevenTimesRatio@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_TenTimesRatioWithItemTooBig@2x.png b/Tests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_TenTimesRatioWithItemTooBig@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_TenTimesRatioWithItemTooBig@2x.png rename to Tests/ReferenceImages_64/ASRatioLayoutSpecSnapshotTests/testRatioLayout_TenTimesRatioWithItemTooBig@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@2x.png b/Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@2x.png rename to Tests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithFlexedMainDimension@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithFlexedMainDimension@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithFlexedMainDimension@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithFlexedMainDimension@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithIndefiniteCrossDimension@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithIndefiniteCrossDimension@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithIndefiniteCrossDimension@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignCenterWithIndefiniteCrossDimension@2x.png diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentCenter@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentCenter@2x.png new file mode 100644 index 0000000000..032e9fac3a Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentCenter@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentEnd@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentEnd@2x.png new file mode 100644 index 0000000000..6d08d18e4a Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentEnd@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentSpaceAround@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentSpaceAround@2x.png new file mode 100644 index 0000000000..032e9fac3a Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentSpaceAround@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentSpaceBetween@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentSpaceBetween@2x.png new file mode 100644 index 0000000000..4df2a81184 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentSpaceBetween@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentStart@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentStart@2x.png new file mode 100644 index 0000000000..4df2a81184 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentOverflow_alignContentStart@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentStretchAndOtherAlignments@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentStretchAndOtherAlignments@2x.png new file mode 100644 index 0000000000..51d060062c Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentStretchAndOtherAlignments@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentCenter@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentCenter@2x.png new file mode 100644 index 0000000000..fea7203a26 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentCenter@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentEnd@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentEnd@2x.png new file mode 100644 index 0000000000..9e0bdad429 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentEnd@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentSpaceAround@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentSpaceAround@2x.png new file mode 100644 index 0000000000..bb608622fd Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentSpaceAround@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentSpaceBetween@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentSpaceBetween@2x.png new file mode 100644 index 0000000000..17b8dbfbc4 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentSpaceBetween@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentStart@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentStart@2x.png new file mode 100644 index 0000000000..ab969dd638 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentStart@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentStretch@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentStretch@2x.png new file mode 100644 index 0000000000..584aca77db Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentUnderflow_alignContentStretch@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentWithUnconstrainedCrossSize@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentWithUnconstrainedCrossSize@2x.png new file mode 100644 index 0000000000..12936781c0 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignContentWithUnconstrainedCrossSize@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedCenter@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedCenter@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedCenter@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedCenter@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedEnd@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedEnd@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedEnd@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedEnd@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStart@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStart@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStart@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStart@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchNoChildExceedsMin@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchNoChildExceedsMin@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchNoChildExceedsMin@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchNoChildExceedsMin@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchOneChildExceedsMin@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchOneChildExceedsMin@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchOneChildExceedsMin@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testAlignedStretchOneChildExceedsMin@2x.png diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png new file mode 100644 index 0000000000..5174b92e27 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png new file mode 100644 index 0000000000..f3e20dbe00 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png new file mode 100644 index 0000000000..e45ab35c68 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png differ diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png new file mode 100644 index 0000000000..6cd4909902 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingAfter@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingAfter@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingAfter@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingAfter@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBalancedOut@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBalancedOut@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBalancedOut@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBalancedOut@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBefore@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBefore@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBefore@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildSpacing_spacingBefore@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildThatChangesCrossSizeWhenMainSizeIsFlexed@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildThatChangesCrossSizeWhenMainSizeIsFlexed@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildThatChangesCrossSizeWhenMainSizeIsFlexed@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testChildThatChangesCrossSizeWhenMainSizeIsFlexed@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_fixedHeight@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_fixedHeight@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_fixedHeight@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_fixedHeight@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_variableHeight@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_variableHeight@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_variableHeight@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisSizeBehaviors_variableHeight@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisStretchingOccursAfterStackAxisFlexing@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisStretchingOccursAfterStackAxisFlexing@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisStretchingOccursAfterStackAxisFlexing@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testCrossAxisStretchingOccursAfterStackAxisFlexing@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testEmptyStack@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testEmptyStack@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testEmptyStack@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testEmptyStack@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_overflow@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_overflow@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_overflow@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_overflow@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_underflow@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_underflow@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_underflow@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisAppliedWhenFlexingItems_underflow@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisOverridesIntrinsicSizeForNonFlexingChildren@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisOverridesIntrinsicSizeForNonFlexingChildren@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisOverridesIntrinsicSizeForNonFlexingChildren@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFixedFlexBasisOverridesIntrinsicSizeForNonFlexingChildren@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_overflow@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_overflow@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_overflow@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_overflow@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_underflow@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_underflow@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_underflow@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFlexWithUnequalIntrinsicSizes_underflow@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalBottomRight@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalBottomRight@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalBottomRight@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalBottomRight@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalCenter@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalCenter@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalCenter@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalCenter@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalTopLeft@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalTopLeft@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalTopLeft@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalTopLeft@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalBottomRight@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalBottomRight@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalBottomRight@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalBottomRight@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalCenter@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalCenter@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalCenter@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalCenter@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalTopLeft@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalTopLeft@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalTopLeft@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalTopLeft@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedCenterWithChildSpacing_variableHeight@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedCenterWithChildSpacing_variableHeight@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedCenterWithChildSpacing_variableHeight@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedCenterWithChildSpacing_variableHeight@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceAroundWithOneChild@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceAroundWithOneChild@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceAroundWithOneChild@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceAroundWithOneChild@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceAroundWithRemainingSpace@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceAroundWithRemainingSpace@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceAroundWithRemainingSpace@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceAroundWithRemainingSpace@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceBetweenWithOneChild@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceBetweenWithOneChild@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceBetweenWithOneChild@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceBetweenWithOneChild@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceBetweenWithRemainingSpace@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceBetweenWithRemainingSpace@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceBetweenWithRemainingSpace@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testJustifiedSpaceBetweenWithRemainingSpace@2x.png diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationAndFlexFactorIsNotRespected@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationAndFlexFactorIsNotRespected@2x.png new file mode 100644 index 0000000000..d5cdcfb602 Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationAndFlexFactorIsNotRespected@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSize@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSize@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSize@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSize@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenChildren@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenChildren@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenChildren@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenChildren@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenWithArbitraryFloats@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenWithArbitraryFloats@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenWithArbitraryFloats@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenWithArbitraryFloats@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactor@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactor@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactor@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactor@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildren@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildren@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildren@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildren@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildrenArbitraryFloats@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildrenArbitraryFloats@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildrenArbitraryFloats@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildrenArbitraryFloats@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZero@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZero@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZero@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZero@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZeroWithArbitraryFloats@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZeroWithArbitraryFloats@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZeroWithArbitraryFloats@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZeroWithArbitraryFloats@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorWithArbitraryFloats@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorWithArbitraryFloats@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorWithArbitraryFloats@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorWithArbitraryFloats@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeWithArbitraryFloats@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeWithArbitraryFloats@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeWithArbitraryFloats@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNegativeViolationIsDistributedBasedOnSizeWithArbitraryFloats@2x.png diff --git a/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png new file mode 100644 index 0000000000..67f32cb4df Binary files /dev/null and b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNestedStackLayoutStretchDoesNotViolateWidth@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNestedStackLayoutStretchDoesNotViolateWidth@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNestedStackLayoutStretchDoesNotViolateWidth@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNestedStackLayoutStretchDoesNotViolateWidth@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_flex@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_flex@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_flex@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_flex@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyCenter@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyCenter@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyCenter@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyCenter@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyEnd@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyEnd@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyEnd@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyEnd@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyStart@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyStart@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyStart@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testOverflowBehaviors_justifyStart@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEqually@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEqually@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEqually@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEqually@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyAmongMixedChildren@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyAmongMixedChildren@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyAmongMixedChildren@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyAmongMixedChildren@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyAmongMixedChildrenWithArbitraryFloats@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyAmongMixedChildrenWithArbitraryFloats@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyAmongMixedChildrenWithArbitraryFloats@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyAmongMixedChildrenWithArbitraryFloats@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyWithArbitraryFloats@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyWithArbitraryFloats@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyWithArbitraryFloats@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedEquallyWithArbitraryFloats@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionally@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionally@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionally@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionally@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyAmongMixedChildren@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyAmongMixedChildren@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyAmongMixedChildren@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyAmongMixedChildren@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyAmongMixedChildrenWithArbitraryFloats@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyAmongMixedChildrenWithArbitraryFloats@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyAmongMixedChildrenWithArbitraryFloats@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyAmongMixedChildrenWithArbitraryFloats@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyWithArbitraryFloats@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyWithArbitraryFloats@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyWithArbitraryFloats@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPositiveViolationIsDistributedProportionallyWithArbitraryFloats@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testRemainingViolationIsAppliedProperlyToFirstFlexibleChild@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testRemainingViolationIsAppliedProperlyToFirstFlexibleChild@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testRemainingViolationIsAppliedProperlyToFirstFlexibleChild@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testRemainingViolationIsAppliedProperlyToFirstFlexibleChild@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testRemainingViolationIsAppliedProperlyToFirstFlexibleChildWithArbitraryFloats@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testRemainingViolationIsAppliedProperlyToFirstFlexibleChildWithArbitraryFloats@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testRemainingViolationIsAppliedProperlyToFirstFlexibleChildWithArbitraryFloats@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testRemainingViolationIsAppliedProperlyToFirstFlexibleChildWithArbitraryFloats@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacingWithChildrenHavingNilObjects_variableHeight@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacingWithChildrenHavingNilObjects_variableHeight@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacingWithChildrenHavingNilObjects_variableHeight@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacingWithChildrenHavingNilObjects_variableHeight@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacing_variableHeight@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacing_variableHeight@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacing_variableHeight@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testStackSpacing_variableHeight@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_flex@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_flex@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_flex@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_flex@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyCenter@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyCenter@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyCenter@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyCenter@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyEnd@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyEnd@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyEnd@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyEnd@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifySpaceAround@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifySpaceAround@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifySpaceAround@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifySpaceAround@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifySpaceBetween@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifySpaceBetween@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifySpaceBetween@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifySpaceBetween@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyStart@2x.png b/Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyStart@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyStart@2x.png rename to Tests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testUnderflowBehaviors_justifyStart@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testShadowing@2x.png b/Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testShadowing@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testShadowing@2x.png rename to Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testShadowing@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInset@2x.png b/Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInset@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInset@2x.png rename to Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInset@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png b/Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png rename to Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png diff --git a/Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png b/Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png new file mode 100644 index 0000000000..5e2062b8e7 Binary files /dev/null and b/Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testThatFastPathTruncationWorks@2x.png b/Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testThatFastPathTruncationWorks@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testThatFastPathTruncationWorks@2x.png rename to Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testThatFastPathTruncationWorks@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testThatSlowPathTruncationWorks@2x.png b/Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testThatSlowPathTruncationWorks@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testThatSlowPathTruncationWorks@2x.png rename to Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testThatSlowPathTruncationWorks@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASWrapperSpecSnapshotTests/testWrapperSpecWithMultipleElementsShouldSizeToLargestElement@2x.png b/Tests/ReferenceImages_64/ASWrapperSpecSnapshotTests/testWrapperSpecWithMultipleElementsShouldSizeToLargestElement@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASWrapperSpecSnapshotTests/testWrapperSpecWithMultipleElementsShouldSizeToLargestElement@2x.png rename to Tests/ReferenceImages_64/ASWrapperSpecSnapshotTests/testWrapperSpecWithMultipleElementsShouldSizeToLargestElement@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASWrapperSpecSnapshotTests/testWrapperSpecWithOneElementShouldSizeToElement@2x.png b/Tests/ReferenceImages_64/ASWrapperSpecSnapshotTests/testWrapperSpecWithOneElementShouldSizeToElement@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASWrapperSpecSnapshotTests/testWrapperSpecWithOneElementShouldSizeToElement@2x.png rename to Tests/ReferenceImages_64/ASWrapperSpecSnapshotTests/testWrapperSpecWithOneElementShouldSizeToElement@2x.png diff --git a/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png b/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png new file mode 100644 index 0000000000..259778c672 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png b/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png new file mode 100644 index 0000000000..9e8286a88c Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png b/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png new file mode 100644 index 0000000000..7f4045ee98 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png b/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png new file mode 100644 index 0000000000..c81f3e9ca4 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png b/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png new file mode 100644 index 0000000000..679b98a52c Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testShadowing@2x.png b/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testShadowing@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testShadowing@2x.png rename to Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testShadowing@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInset@2x.png b/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInset@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInset@2x.png rename to Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInset@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png b/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png rename to Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png diff --git a/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png b/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png new file mode 100644 index 0000000000..65c801d4df Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testThatFastPathTruncationWorks@2x.png b/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testThatFastPathTruncationWorks@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testThatFastPathTruncationWorks@2x.png rename to Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testThatFastPathTruncationWorks@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testThatSlowPathTruncationWorks@2x.png b/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testThatSlowPathTruncationWorks@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testThatSlowPathTruncationWorks@2x.png rename to Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testThatSlowPathTruncationWorks@2x.png diff --git a/AsyncDisplayKitTestHost/AppDelegate.h b/Tests/TestHost/AppDelegate.h similarity index 100% rename from AsyncDisplayKitTestHost/AppDelegate.h rename to Tests/TestHost/AppDelegate.h diff --git a/AsyncDisplayKitTestHost/AppDelegate.mm b/Tests/TestHost/AppDelegate.m similarity index 100% rename from AsyncDisplayKitTestHost/AppDelegate.mm rename to Tests/TestHost/AppDelegate.m diff --git a/Default-568h@2x.png b/Tests/TestHost/Default-568h@2x.png similarity index 100% rename from Default-568h@2x.png rename to Tests/TestHost/Default-568h@2x.png diff --git a/AsyncDisplayKitTestHost/Info.plist b/Tests/TestHost/Info.plist similarity index 100% rename from AsyncDisplayKitTestHost/Info.plist rename to Tests/TestHost/Info.plist diff --git a/AsyncDisplayKitTestHost/main.m b/Tests/TestHost/main.m similarity index 100% rename from AsyncDisplayKitTestHost/main.m rename to Tests/TestHost/main.m diff --git a/AsyncDisplayKitTests/TestResources/ASThrashTestRecordedCase b/Tests/TestResources/ASThrashTestRecordedCase similarity index 100% rename from AsyncDisplayKitTests/TestResources/ASThrashTestRecordedCase rename to Tests/TestResources/ASThrashTestRecordedCase diff --git a/AsyncDisplayKitTests/TestResources/AttributedStringsFixture0.plist b/Tests/TestResources/AttributedStringsFixture0.plist similarity index 100% rename from AsyncDisplayKitTests/TestResources/AttributedStringsFixture0.plist rename to Tests/TestResources/AttributedStringsFixture0.plist diff --git a/AsyncDisplayKitTests/TestResources/logo-square.png b/Tests/TestResources/logo-square.png similarity index 100% rename from AsyncDisplayKitTests/TestResources/logo-square.png rename to Tests/TestResources/logo-square.png diff --git a/AsyncDisplayKitTests/en.lproj/InfoPlist.strings b/Tests/en.lproj/InfoPlist.strings similarity index 100% rename from AsyncDisplayKitTests/en.lproj/InfoPlist.strings rename to Tests/en.lproj/InfoPlist.strings diff --git a/buck-files/BUCK_FBSnapshotTestCase b/buck-files/BUCK_FBSnapshotTestCase new file mode 100755 index 0000000000..c8b969639e --- /dev/null +++ b/buck-files/BUCK_FBSnapshotTestCase @@ -0,0 +1,12 @@ +apple_library( + name = 'FBSnapshotTestCase', + exported_headers = glob(['FBSnapshotTestCase' + '/**/*.h']), + srcs = glob(['FBSnapshotTestCase' + '/**/*.m']), + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + '$SDKROOT/System/Library/Frameworks/UIKit.framework', + '$SDKROOT/System/Library/Frameworks/QuartzCore.framework', + '$PLATFORM_DIR/Developer/Library/Frameworks/XCTest.framework', + ], + visibility = ['PUBLIC'], +) diff --git a/buck-files/BUCK_FLAnimatedImage b/buck-files/BUCK_FLAnimatedImage new file mode 100755 index 0000000000..f04abd396b --- /dev/null +++ b/buck-files/BUCK_FLAnimatedImage @@ -0,0 +1,18 @@ +apple_library( + name = 'FLAnimatedImage', + exported_headers = glob(['FLAnimatedImage/*.h']), + srcs = glob(['FLAnimatedImage/*.m']), + preprocessor_flags = ['-fobjc-arc', '-Wno-deprecated-declarations'], + lang_preprocessor_flags = { + 'C': ['-std=gnu99'], + 'CXX': ['-std=gnu++11', '-stdlib=libc++'], + }, + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + '$SDKROOT/System/Library/Frameworks/UIKit.framework', + '$SDKROOT/System/Library/Frameworks/ImageIO.framework', + '$SDKROOT/System/Library/Frameworks/MobileCoreServices.framework', + '$SDKROOT/System/Library/Frameworks/QuartzCore.framework', + ], + visibility = ['PUBLIC'], +) diff --git a/buck-files/BUCK_JGMethodSwizzler b/buck-files/BUCK_JGMethodSwizzler new file mode 100755 index 0000000000..169cfa1e01 --- /dev/null +++ b/buck-files/BUCK_JGMethodSwizzler @@ -0,0 +1,9 @@ +apple_library( + name = 'JGMethodSwizzler', + exported_headers = ['JGMethodSwizzler' + '/JGMethodSwizzler.h'], + srcs = ['JGMethodSwizzler' + '/JGMethodSwizzler.m'], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + ], + visibility = ['PUBLIC'], +) diff --git a/buck-files/BUCK_OCMock b/buck-files/BUCK_OCMock new file mode 100755 index 0000000000..666f844582 --- /dev/null +++ b/buck-files/BUCK_OCMock @@ -0,0 +1,9 @@ +apple_library( + name = 'OCMock', + exported_headers = glob(['Source/OCMock' + '/*.h']), + srcs = glob(['Source/OCMock' + '/*.m']), + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + ], + visibility = ['PUBLIC'], +) diff --git a/buck-files/BUCK_PINCache b/buck-files/BUCK_PINCache new file mode 100755 index 0000000000..660b69f716 --- /dev/null +++ b/buck-files/BUCK_PINCache @@ -0,0 +1,23 @@ +apple_library( + name = 'PINCache', + exported_headers = glob(['PINCache/*.h']), + # PINDiskCache.m should be compiled with '-fobjc-arc-exceptions' (#105) + srcs = + glob(['PINCache/*.m'], excludes = ['PINCache/PINDiskCache.m']) + + [('PINCache/PINDiskCache.m', ['-fobjc-arc-exceptions'])], + preprocessor_flags = ['-fobjc-arc'], + lang_preprocessor_flags = { + 'C': ['-std=gnu99'], + 'CXX': ['-std=gnu++11', '-stdlib=libc++'], + }, + linker_flags = [ + '-weak_framework', + 'UIKit', + '-weak_framework', + 'AppKit', + ], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + ], + visibility = ['PUBLIC'], +) diff --git a/buck-files/BUCK_PINRemoteImage b/buck-files/BUCK_PINRemoteImage new file mode 100755 index 0000000000..95825e4d95 --- /dev/null +++ b/buck-files/BUCK_PINRemoteImage @@ -0,0 +1,93 @@ +##################################### +# Defines +##################################### +COMMON_PREPROCESSOR_FLAGS = ['-fobjc-arc'] + +COMMON_LANG_PREPROCESSOR_FLAGS = { + 'C': ['-std=gnu99'], + 'CXX': ['-std=gnu++11', '-stdlib=libc++'], +} + +FLANIMATEDIMAGE_HEADER_FILES = ['Pod/Classes/Image Categories/FLAnimatedImageView+PINRemoteImage.h'] +FLANIMATEDIMAGE_SOURCE_FILES = ['Pod/Classes/Image Categories/FLAnimatedImageView+PINRemoteImage.m'] + +PINCACHE_HEADER_FILES = glob(['Pod/Classes/PINCache/**/*.h']) +PINCACHE_SOURCE_FILES = glob(['Pod/Classes/PINCache/**/*.m']) + +##################################### +# PINRemoteImage core targets +##################################### +apple_library( + name = 'PINRemoteImage-Core', + header_path_prefix = 'PINRemoteImage', + exported_headers = glob([ + 'Pod/Classes/**/*.h', + ], + excludes = FLANIMATEDIMAGE_HEADER_FILES + PINCACHE_HEADER_FILES + ), + srcs = glob([ + 'Pod/Classes/**/*.m', + ], + excludes = FLANIMATEDIMAGE_SOURCE_FILES + PINCACHE_SOURCE_FILES + ), + preprocessor_flags = COMMON_PREPROCESSOR_FLAGS + [ + '-DPIN_TARGET_IOS=(TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR || TARGET_OS_TV)', + '-DPIN_TARGET_MAC=(TARGET_OS_MAC)', + ], + lang_preprocessor_flags = COMMON_LANG_PREPROCESSOR_FLAGS, + linker_flags = [ + '-weak_framework', + 'UIKit', + '-weak_framework', + 'MobileCoreServices', + '-weak_framework', + 'Cocoa', + '-weak_framework', + 'CoreServices', + ], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/ImageIO.framework', + '$SDKROOT/System/Library/Frameworks/Accelerate.framework', + ], + visibility = ['PUBLIC'], +) + +apple_library( + name = 'PINRemoteImage', + deps = [ + ':PINRemoteImage-FLAnimatedImage', + ':PINRemoteImage-PINCache' + ], + visibility = ['PUBLIC'], +) + +##################################### +# Other PINRemoteImage targets +##################################### +apple_library( + name = 'PINRemoteImage-FLAnimatedImage', + header_path_prefix = 'PINRemoteImage', + exported_headers = FLANIMATEDIMAGE_HEADER_FILES, + srcs = FLANIMATEDIMAGE_SOURCE_FILES, + preprocessor_flags = COMMON_PREPROCESSOR_FLAGS, + deps = [ + ':PINRemoteImage-Core', + '//Pods/FLAnimatedImage:FLAnimatedImage' + ], + visibility = ['PUBLIC'], +) + +apple_library( + name = 'PINRemoteImage-PINCache', + header_path_prefix = 'PINRemoteImage', + exported_headers = PINCACHE_HEADER_FILES, + srcs = PINCACHE_SOURCE_FILES, + preprocessor_flags = COMMON_PREPROCESSOR_FLAGS, + deps = [ + ':PINRemoteImage-Core', + '//Pods/PINCache:PINCache' + ], + visibility = ['PUBLIC'], +) + +#TODO WebP variants diff --git a/build.sh b/build.sh index 0c6ed44c77..d451bc9ad7 100755 --- a/build.sh +++ b/build.sh @@ -1,19 +1,65 @@ #!/bin/bash -# **** Update me when new Xcode versions are released! **** -PLATFORM="platform=iOS Simulator,OS=10.1,name=iPhone 7" -SDK="iphonesimulator10.1" +PLATFORM="platform=iOS Simulator,name=iPhone 7" +SDK="iphonesimulator" +DERIVED_DATA_PATH="~/ASDKDerivedData" # It is pitch black. set -e -function trap_handler() { +function trap_handler { echo -e "\n\nOh no! You walked directly into the slavering fangs of a lurking grue!" echo "**** You have died ****" exit 255 } trap trap_handler INT TERM EXIT +# Derived data handling +eval [ ! -d $DERIVED_DATA_PATH ] && eval mkdir $DERIVED_DATA_PATH +function clean_derived_data { + eval find $DERIVED_DATA_PATH -mindepth 1 -delete +} + +# Build example +function build_example { + example="$1" + + clean_derived_data + + if [ -f "${example}/Podfile" ]; then + echo "Using CocoaPods" + if [ -f "${example}/Podfile.lock" ]; then + rm "$example/Podfile.lock" + fi + rm -rf "$example/Pods" + pod install --project-directory=$example + + set -o pipefail && xcodebuild \ + -workspace "${example}/Sample.xcworkspace" \ + -scheme Sample \ + -sdk "$SDK" \ + -destination "$PLATFORM" \ + -derivedDataPath "$DERIVED_DATA_PATH" \ + build | xcpretty $FORMATTER + elif [ -f "${example}/Cartfile" ]; then + echo "Using Carthage" + local_repo=`pwd` + current_branch=`git rev-parse --abbrev-ref HEAD` + cd $example + + echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile" + carthage update --platform iOS + + set -o pipefail && xcodebuild \ + -project "Sample.xcodeproj" \ + -scheme Sample \ + -sdk "$SDK" \ + -destination "$PLATFORM" \ + build | xcpretty $FORMATTER + + cd ../.. + fi +} MODE="$1" @@ -31,7 +77,20 @@ if [ "$MODE" = "tests" ]; then -scheme AsyncDisplayKit \ -sdk "$SDK" \ -destination "$PLATFORM" \ - build test | xcpretty $FORMATTER + build-for-testing test | xcpretty $FORMATTER + trap - EXIT + exit 0 +fi + +if [ "$MODE" = "tests_listkit" ]; then + echo "Building & testing AsyncDisplayKit+IGListKit." + pod install --project-directory=ASDKListKit + set -o pipefail && xcodebuild \ + -workspace ASDKListKit/ASDKListKit.xcworkspace \ + -scheme ASDKListKitTests \ + -sdk "$SDK" \ + -destination "$PLATFORM" \ + build-for-testing test | xcpretty $FORMATTER trap - EXIT exit 0 fi @@ -44,39 +103,7 @@ if [ "$MODE" = "examples" ]; then for example in examples/*/; do echo "Building (examples) $example." - if [ -f "${example}/Podfile" ]; then - echo "Using CocoaPods" - if [ -f "${example}/Podfile.lock" ]; then - rm "$example/Podfile.lock" - fi - rm -rf "$example/Pods" - pod install --project-directory=$example - - set -o pipefail && xcodebuild \ - -workspace "${example}/Sample.xcworkspace" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - -derivedDataPath ~/ \ - build | xcpretty $FORMATTER - elif [ -f "${example}/Cartfile" ]; then - echo "Using Carthage" - local_repo=`pwd` - current_branch=`git rev-parse --abbrev-ref HEAD` - cd $example - - echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile" - carthage update --platform iOS - - set -o pipefail && xcodebuild \ - -project "Sample.xcodeproj" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build | xcpretty $FORMATTER - - cd ../.. - fi + build_example $example done trap - EXIT exit 0 @@ -90,39 +117,7 @@ if [ "$MODE" = "examples-pt1" ]; then for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -6 | head); do echo "Building (examples-pt1) $example." - if [ -f "${example}/Podfile" ]; then - echo "Using CocoaPods" - if [ -f "${example}/Podfile.lock" ]; then - rm "$example/Podfile.lock" - fi - rm -rf "$example/Pods" - pod install --project-directory=$example - - set -o pipefail && xcodebuild \ - -workspace "${example}/Sample.xcworkspace" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - -derivedDataPath ~/ \ - build | xcpretty $FORMATTER - elif [ -f "${example}/Cartfile" ]; then - echo "Using Carthage" - local_repo=`pwd` - current_branch=`git rev-parse --abbrev-ref HEAD` - cd $example - - echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile" - carthage update --platform iOS - - set -o pipefail && xcodebuild \ - -project "Sample.xcodeproj" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build | xcpretty $FORMATTER - - cd ../.. - fi + build_example $example done trap - EXIT exit 0 @@ -136,39 +131,7 @@ if [ "$MODE" = "examples-pt2" ]; then for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -12 | tail -6 | head); do echo "Building $example (examples-pt2)." - if [ -f "${example}/Podfile" ]; then - echo "Using CocoaPods" - if [ -f "${example}/Podfile.lock" ]; then - rm "$example/Podfile.lock" - fi - rm -rf "$example/Pods" - pod install --project-directory=$example - - set -o pipefail && xcodebuild \ - -workspace "${example}/Sample.xcworkspace" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - -derivedDataPath ~/ \ - build | xcpretty $FORMATTER - elif [ -f "${example}/Cartfile" ]; then - echo "Using Carthage" - local_repo=`pwd` - current_branch=`git rev-parse --abbrev-ref HEAD` - cd $example - - echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile" - carthage update --platform iOS - - set -o pipefail && xcodebuild \ - -project "Sample.xcodeproj" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build | xcpretty $FORMATTER - - cd ../.. - fi + build_example $example done trap - EXIT exit 0 @@ -182,39 +145,7 @@ if [ "$MODE" = "examples-pt3" ]; then for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -7 | head); do echo "Building $example (examples-pt3)." - if [ -f "${example}/Podfile" ]; then - echo "Using CocoaPods" - if [ -f "${example}/Podfile.lock" ]; then - rm "$example/Podfile.lock" - fi - rm -rf "$example/Pods" - pod install --project-directory=$example - - set -o pipefail && xcodebuild \ - -workspace "${example}/Sample.xcworkspace" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - -derivedDataPath ~/ \ - build | xcpretty $FORMATTER - elif [ -f "${example}/Cartfile" ]; then - echo "Using Carthage" - local_repo=`pwd` - current_branch=`git rev-parse --abbrev-ref HEAD` - cd $example - - echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile" - carthage update --platform iOS - - set -o pipefail && xcodebuild \ - -project "Sample.xcodeproj" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build | xcpretty $FORMATTER - - cd ../.. - fi + build_example $example done trap - EXIT exit 0 @@ -228,44 +159,23 @@ if [ "$MODE" = "examples-extra" ]; then for example in $((find ./examples_extra -type d -maxdepth 1 \( ! -iname ".*" \)) | head -7 | head); do echo "Building $example (examples-extra)." - if [ -f "${example}/Podfile" ]; then - echo "Using CocoaPods" - if [ -f "${example}/Podfile.lock" ]; then - rm "$example/Podfile.lock" - fi - rm -rf "$example/Pods" - pod install --project-directory=$example - - set -o pipefail && xcodebuild \ - -workspace "${example}/Sample.xcworkspace" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - -derivedDataPath ~/ \ - build | xcpretty $FORMATTER - elif [ -f "${example}/Cartfile" ]; then - echo "Using Carthage" - local_repo=`pwd` - current_branch=`git rev-parse --abbrev-ref HEAD` - cd $example - - echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile" - carthage update --platform iOS - - set -o pipefail && xcodebuild \ - -project "Sample.xcodeproj" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build | xcpretty $FORMATTER - - cd ../.. - fi + build_example $example done trap - EXIT exit 0 fi +# Support building a specific example: sh build.sh example examples/ASDKLayoutTransition +if [ "$MODE" = "example" ]; then + echo "Verifying that all AsyncDisplayKit examples compile." + #Update cocoapods repo + pod repo update master + + build_example $2 + trap - EXIT + exit 0 +fi + if [ "$MODE" = "life-without-cocoapods" ]; then echo "Verifying that AsyncDisplayKit functions as a static library." @@ -292,4 +202,18 @@ if [ "$MODE" = "framework" ]; then exit 0 fi +if [ "$MODE" = "cocoapods-lint" ]; then + echo "Verifying that podspec lints." + + set -o pipefail && pod env && pod lib lint + trap - EXIT + exit 0 +fi + +if [ "$MODE" = "carthage" ]; then + echo "Verifying carthage works." + + set -o pipefail && carthage update && carthage build --no-skip-current +fi + echo "Unrecognised mode '$MODE'." diff --git a/examples/ASCollectionView/Podfile b/examples/ASCollectionView/Podfile index 5c30ce798e..7a8d8c1a00 100644 --- a/examples/ASCollectionView/Podfile +++ b/examples/ASCollectionView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index e4c94105dd..d00ea60df6 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -21,7 +21,7 @@ #import "SupplementaryNode.h" #import "ItemNode.h" -@interface ViewController () +@interface ViewController () @property (nonatomic, strong) ASCollectionNode *collectionNode; @property (nonatomic, strong) NSArray *data; @@ -44,25 +44,15 @@ - (void)viewDidLoad { [super viewDidLoad]; - UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - layout.headerReferenceSize = CGSizeMake(50.0, 50.0); - layout.footerReferenceSize = CGSizeMake(50.0, 50.0); - - // This method is deprecated because we reccommend using ASCollectionNode instead of ASCollectionView. - // This functionality & example project remains for users who insist on using ASCollectionView. - self.collectionNode = [[ASCollectionNode alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; + self.collectionNode = [[ASCollectionNode alloc] initWithLayoutDelegate:[[ASCollectionFlowLayoutDelegate alloc] init] layoutFacilitator:nil]; self.collectionNode.dataSource = self; self.collectionNode.delegate = self; self.collectionNode.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.collectionNode.backgroundColor = [UIColor whiteColor]; - // This method is deprecated because we reccommend using ASCollectionNode instead of ASCollectionView. - // This functionality & example project remains for users who insist on using ASCollectionView. - [self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; - [self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter]; - [self.view addSubnode:self.collectionNode]; + self.collectionNode.frame = self.view.bounds; #if !SIMULATE_WEB_RESPONSE self.navigationItem.leftItemsSupplementBackButton = YES; diff --git a/examples/ASDKLayoutTransition/Podfile b/examples/ASDKLayoutTransition/Podfile index 919de4b311..32b4c3336e 100644 --- a/examples/ASDKLayoutTransition/Podfile +++ b/examples/ASDKLayoutTransition/Podfile @@ -1,5 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do - pod 'AsyncDisplayKit', :path => '../..' + pod 'AsyncDisplayKit', :path => '../..' + pod 'AsyncDisplayKit/Yoga', :path => '../..' end diff --git a/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj index a7693ccc5c..5de473f6a0 100644 --- a/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj @@ -320,7 +320,6 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -335,7 +334,6 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/examples/ASDKLayoutTransition/Sample/ViewController.m b/examples/ASDKLayoutTransition/Sample/ViewController.m index 789d5029c4..dd8d375d13 100644 --- a/examples/ASDKLayoutTransition/Sample/ViewController.m +++ b/examples/ASDKLayoutTransition/Sample/ViewController.m @@ -53,6 +53,7 @@ - (instancetype)init _textNodeTwo = [[ASTextNode alloc] init]; _textNodeTwo.attributedText = [[NSAttributedString alloc] initWithString:@"It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English."]; + ASSetDebugNames(_textNodeOne, _textNodeTwo); // Setup button NSString *buttonTitle = @"Start Layout Transition"; @@ -60,8 +61,8 @@ - (instancetype)init UIColor *buttonColor = [UIColor blueColor]; _buttonNode = [[ASButtonNode alloc] init]; - [_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:buttonColor forState:ASControlStateNormal]; - [_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:[buttonColor colorWithAlphaComponent:0.5] forState:ASControlStateHighlighted]; + [_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:buttonColor forState:UIControlStateNormal]; + [_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:[buttonColor colorWithAlphaComponent:0.5] forState:UIControlStateHighlighted]; // Some debug colors diff --git a/examples/ASDKTube/Podfile b/examples/ASDKTube/Podfile index 919de4b311..defaf55058 100644 --- a/examples/ASDKTube/Podfile +++ b/examples/ASDKTube/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/ASDKTube/Sample/Models/VideoModel.m b/examples/ASDKTube/Sample/Models/VideoModel.m index 2db920f179..ee82898cbd 100644 --- a/examples/ASDKTube/Sample/Models/VideoModel.m +++ b/examples/ASDKTube/Sample/Models/VideoModel.m @@ -24,7 +24,7 @@ - (instancetype)init { self = [super init]; if (self) { - NSString *videoUrlString = @"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-3045b261-7e93-4492-b7e5-5d6358376c9f-editedLiveAndDie.mov"; + NSString *videoUrlString = @"https://www.w3schools.com/html/mov_bbb.mp4"; NSString *avatarUrlString = [NSString stringWithFormat:@"https://api.adorable.io/avatars/50/%@",[[NSProcessInfo processInfo] globallyUniqueString]]; _title = @"Demo title"; diff --git a/examples/ASDKTube/Sample/Nodes/VideoContentCell.m b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m index ca214c7af1..8f57bea3cd 100644 --- a/examples/ASDKTube/Sample/Nodes/VideoContentCell.m +++ b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m @@ -69,7 +69,7 @@ - (instancetype)initWithVideoObject:(VideoModel *)video _muteButtonNode.style.height = ASDimensionMakeWithPoints(22.0); [_muteButtonNode addTarget:self action:@selector(didTapMuteButton) forControlEvents:ASControlNodeEventTouchUpInside]; - _videoPlayerNode = [[ASVideoPlayerNode alloc] initWithUrl:_videoModel.url loadAssetWhenNodeBecomesVisible:YES]; + _videoPlayerNode = [[ASVideoPlayerNode alloc] initWithURL:_videoModel.url]; _videoPlayerNode.delegate = self; _videoPlayerNode.backgroundColor = [UIColor blackColor]; [self addSubnode:_videoPlayerNode]; @@ -126,9 +126,9 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize - (void)setMuteButtonIcon { if (_videoPlayerNode.muted) { - [_muteButtonNode setImage:[UIImage imageNamed:@"ico-mute"] forState:ASControlStateNormal]; + [_muteButtonNode setImage:[UIImage imageNamed:@"ico-mute"] forState:UIControlStateNormal]; } else { - [_muteButtonNode setImage:[UIImage imageNamed:@"ico-unmute"] forState:ASControlStateNormal]; + [_muteButtonNode setImage:[UIImage imageNamed:@"ico-unmute"] forState:UIControlStateNormal]; } } @@ -142,7 +142,6 @@ - (void)didTapMuteButton - (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer { if (_videoPlayerNode.playerState == ASVideoNodePlayerStatePlaying) { - NSLog(@"TRANSITION"); _videoPlayerNode.controlsDisabled = !_videoPlayerNode.controlsDisabled; [_videoPlayerNode pause]; } else { diff --git a/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m index 537d442147..9cd1e72d98 100644 --- a/examples/ASDKTube/Sample/ViewController.m +++ b/examples/ASDKTube/Sample/ViewController.m @@ -87,9 +87,9 @@ - (ASVideoPlayerNode *)videoPlayerNode; return _videoPlayerNode; } - NSURL *fileUrl = [NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-3045b261-7e93-4492-b7e5-5d6358376c9f-editedLiveAndDie.mov"]; + NSURL *fileUrl = [NSURL URLWithString:@"https://www.w3schools.com/html/mov_bbb.mp4"]; - _videoPlayerNode = [[ASVideoPlayerNode alloc] initWithUrl:fileUrl]; + _videoPlayerNode = [[ASVideoPlayerNode alloc] initWithURL:fileUrl]; _videoPlayerNode.delegate = self; // _videoPlayerNode.disableControls = YES; // diff --git a/examples/ASDKgram/Podfile b/examples/ASDKgram/Podfile index 5c30ce798e..c401cd001c 100644 --- a/examples/ASDKgram/Podfile +++ b/examples/ASDKgram/Podfile @@ -1,6 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do - pod 'AsyncDisplayKit', :path => '../..' + pod 'AsyncDisplayKit/IGListKit', :path => '../..' + pod 'AsyncDisplayKit/PINRemoteImage', :path => '../..' end - diff --git a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj index dbbd72d54c..aab6d55e44 100644 --- a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj @@ -31,12 +31,19 @@ 768843931CAA37EF00D8629E /* UserModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688437B1CAA37EF00D8629E /* UserModel.m */; }; 768843961CAA37EF00D8629E /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688437E1CAA37EF00D8629E /* Utilities.m */; }; B13424EE6D36C2EC5D1030B6 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AD5DDA0A29B0F32AA5CC47BA /* libPods-Sample.a */; }; + CC00D1571E15912F004E5502 /* PhotoFeedListKitViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CC00D1561E15912F004E5502 /* PhotoFeedListKitViewController.m */; }; + CC5369AC1E15925200FAD348 /* PhotoFeedSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5369AB1E15925200FAD348 /* PhotoFeedSectionController.m */; }; + CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5532161E15CC1E0011C01F /* ASCollectionSectionController.m */; }; + CC6350BB1E1C482D002BC613 /* TailLoadingNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC6350BA1E1C482D002BC613 /* TailLoadingNode.m */; }; + CC85250F1E36B392008EABE6 /* FeedHeaderNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC85250E1E36B392008EABE6 /* FeedHeaderNode.m */; }; + E5F128F01E09625400B4335F /* PhotoFeedBaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 69CE83D91E515036004AA230 /* PhotoFeedControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedControllerProtocol.h; sourceTree = ""; }; 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; 76229A761CBB79E000B62CEF /* WindowWithStatusBarUnderlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowWithStatusBarUnderlay.h; sourceTree = ""; }; @@ -80,7 +87,20 @@ 7688437F1CAA37EF00D8629E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 97A9B1BAF4265967672F9EA3 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; AD5DDA0A29B0F32AA5CC47BA /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + CC00D1551E15912F004E5502 /* PhotoFeedListKitViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedListKitViewController.h; sourceTree = ""; }; + CC00D1561E15912F004E5502 /* PhotoFeedListKitViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoFeedListKitViewController.m; sourceTree = ""; }; + CC5369AA1E15925200FAD348 /* PhotoFeedSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedSectionController.h; sourceTree = ""; }; + CC5369AB1E15925200FAD348 /* PhotoFeedSectionController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoFeedSectionController.m; sourceTree = ""; }; + CC5532111E159D770011C01F /* RefreshingSectionControllerType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RefreshingSectionControllerType.h; sourceTree = ""; }; + CC5532151E15CC1E0011C01F /* ASCollectionSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionSectionController.h; sourceTree = ""; }; + CC5532161E15CC1E0011C01F /* ASCollectionSectionController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionSectionController.m; sourceTree = ""; }; + CC6350B91E1C482D002BC613 /* TailLoadingNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TailLoadingNode.h; sourceTree = ""; }; + CC6350BA1E1C482D002BC613 /* TailLoadingNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TailLoadingNode.m; sourceTree = ""; }; + CC85250D1E36B392008EABE6 /* FeedHeaderNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeedHeaderNode.h; sourceTree = ""; }; + CC85250E1E36B392008EABE6 /* FeedHeaderNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeedHeaderNode.m; sourceTree = ""; }; D09B5DF0BFB37583DE8F3142 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + E5F128EE1E09612700B4335F /* PhotoFeedBaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedBaseController.h; sourceTree = ""; }; + E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoFeedBaseController.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -167,8 +187,12 @@ 767A5F141CAA3D8A004CDA8D /* Controller */ = { isa = PBXGroup; children = ( + 69CE83D91E515036004AA230 /* PhotoFeedControllerProtocol.h */, + E5F128EE1E09612700B4335F /* PhotoFeedBaseController.h */, + E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */, 767A5F161CAA3D96004CDA8D /* UIKit */, 767A5F151CAA3D90004CDA8D /* ASDK */, + CC00D1581E159132004E5502 /* ASDK-ListKit */, ); name = Controller; sourceTree = ""; @@ -239,6 +263,10 @@ 767A5F1A1CAA3DBF004CDA8D /* ASDK */ = { isa = PBXGroup; children = ( + CC85250D1E36B392008EABE6 /* FeedHeaderNode.h */, + CC85250E1E36B392008EABE6 /* FeedHeaderNode.m */, + CC6350B91E1C482D002BC613 /* TailLoadingNode.h */, + CC6350BA1E1C482D002BC613 /* TailLoadingNode.m */, 7688435B1CAA37EF00D8629E /* PhotoCellNode.h */, 768843731CAA37EF00D8629E /* PhotoCellNode.m */, 768843541CAA37EF00D8629E /* CommentsNode.h */, @@ -247,6 +275,20 @@ name = ASDK; sourceTree = ""; }; + CC00D1581E159132004E5502 /* ASDK-ListKit */ = { + isa = PBXGroup; + children = ( + CC5532111E159D770011C01F /* RefreshingSectionControllerType.h */, + CC00D1551E15912F004E5502 /* PhotoFeedListKitViewController.h */, + CC00D1561E15912F004E5502 /* PhotoFeedListKitViewController.m */, + CC5369AA1E15925200FAD348 /* PhotoFeedSectionController.h */, + CC5369AB1E15925200FAD348 /* PhotoFeedSectionController.m */, + CC5532151E15CC1E0011C01F /* ASCollectionSectionController.h */, + CC5532161E15CC1E0011C01F /* ASCollectionSectionController.m */, + ); + name = "ASDK-ListKit"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -377,16 +419,22 @@ 768843821CAA37EF00D8629E /* CommentModel.m in Sources */, 768843831CAA37EF00D8629E /* CommentsNode.m in Sources */, 768843961CAA37EF00D8629E /* Utilities.m in Sources */, + E5F128F01E09625400B4335F /* PhotoFeedBaseController.m in Sources */, 768843931CAA37EF00D8629E /* UserModel.m in Sources */, + CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */, 768843801CAA37EF00D8629E /* AppDelegate.m in Sources */, 768843811CAA37EF00D8629E /* CommentFeedModel.m in Sources */, 7688438E1CAA37EF00D8629E /* PhotoFeedNodeController.m in Sources */, + CC6350BB1E1C482D002BC613 /* TailLoadingNode.m in Sources */, + CC85250F1E36B392008EABE6 /* FeedHeaderNode.m in Sources */, 768843841CAA37EF00D8629E /* CommentView.m in Sources */, 768843881CAA37EF00D8629E /* LocationModel.m in Sources */, 768843901CAA37EF00D8629E /* PhotoModel.m in Sources */, 768843911CAA37EF00D8629E /* PhotoTableViewCell.m in Sources */, + CC00D1571E15912F004E5502 /* PhotoFeedListKitViewController.m in Sources */, 7688438B1CAA37EF00D8629E /* PhotoCellNode.m in Sources */, 7688438D1CAA37EF00D8629E /* PhotoFeedModel.m in Sources */, + CC5369AC1E15925200FAD348 /* PhotoFeedSectionController.m in Sources */, 768843851CAA37EF00D8629E /* ImageURLModel.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -482,7 +530,6 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Sample/Sample.pch; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -502,7 +549,6 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Sample/Sample.pch; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/examples/ASDKgram/Sample/ASCollectionSectionController.h b/examples/ASDKgram/Sample/ASCollectionSectionController.h new file mode 100644 index 0000000000..f69791bc76 --- /dev/null +++ b/examples/ASDKgram/Sample/ASCollectionSectionController.h @@ -0,0 +1,28 @@ +// +// ASCollectionSectionController.h +// Sample +// +// Created by Adlai Holler on 12/29/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASCollectionSectionController : IGListSectionController + +/** + * The items managed by this section controller. + */ +@property (nonatomic, strong, readonly) NSArray> *items; + +- (void)setItems:(NSArray> *)newItems + animated:(BOOL)animated + completion:(nullable void(^)())completion; + +- (NSInteger)numberOfItems; + +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/ASDKgram/Sample/ASCollectionSectionController.m b/examples/ASDKgram/Sample/ASCollectionSectionController.m new file mode 100644 index 0000000000..27284a1e99 --- /dev/null +++ b/examples/ASDKgram/Sample/ASCollectionSectionController.m @@ -0,0 +1,79 @@ +// +// ASCollectionSectionController.m +// Sample +// +// Created by Adlai Holler on 12/29/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASCollectionSectionController.h" +#import + +@interface ASCollectionSectionController () +@property (nonatomic, strong, readonly) dispatch_queue_t diffingQueue; + +/// The items that have been diffed and are waiting to be submitted to the collection view. +/// Should always be accessed on the diffing queue, and should never be accessed +/// before the initial items are read (in -numberOfItems). +@property (nonatomic, copy) NSArray *pendingItems; + +@property (nonatomic) BOOL initialItemsRead; +@end + +@implementation ASCollectionSectionController +@synthesize diffingQueue = _diffingQueue; + +- (NSInteger)numberOfItems +{ + if (_initialItemsRead == NO) { + _pendingItems = self.items; + _initialItemsRead = YES; + } + return self.items.count; +} + +- (dispatch_queue_t)diffingQueue +{ + if (_diffingQueue == nil) { + _diffingQueue = dispatch_queue_create("ASCollectionSectionController.diffingQueue", DISPATCH_QUEUE_SERIAL); + } + return _diffingQueue; +} + +- (void)setItems:(NSArray *)newItems animated:(BOOL)animated completion:(void(^)())completion +{ + ASDisplayNodeAssertMainThread(); + newItems = [newItems copy]; + if (!self.initialItemsRead) { + _items = newItems; + if (completion) { + completion(); + } + return; + } + + BOOL wasEmpty = (self.items.count == 0); + + dispatch_async(self.diffingQueue, ^{ + IGListIndexSetResult *result = IGListDiff(self.pendingItems, newItems, IGListDiffPointerPersonality); + self.pendingItems = newItems; + dispatch_async(dispatch_get_main_queue(), ^{ + id ctx = self.collectionContext; + [ctx performBatchAnimated:animated updates:^{ + [ctx insertInSectionController:(id)self atIndexes:result.inserts]; + [ctx deleteInSectionController:(id)self atIndexes:result.deletes]; + _items = newItems; + } completion:^(BOOL finished) { + if (completion) { + completion(); + } + // WORKAROUND for https://github.com/Instagram/IGListKit/issues/378 + if (wasEmpty) { + [(IGListAdapter *)ctx performUpdatesAnimated:NO completion:nil]; + } + }]; + }); + }); +} + +@end diff --git a/examples/ASDKgram/Sample/AppDelegate.h b/examples/ASDKgram/Sample/AppDelegate.h index cd0d7065ab..f5a8485f9b 100644 --- a/examples/ASDKgram/Sample/AppDelegate.h +++ b/examples/ASDKgram/Sample/AppDelegate.h @@ -17,10 +17,6 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -@protocol PhotoFeedControllerProtocol -- (void)resetAllData; -@end - @interface AppDelegate : UIResponder @end diff --git a/examples/ASDKgram/Sample/AppDelegate.m b/examples/ASDKgram/Sample/AppDelegate.m index fe04d8e776..ce3c4a332c 100644 --- a/examples/ASDKgram/Sample/AppDelegate.m +++ b/examples/ASDKgram/Sample/AppDelegate.m @@ -20,6 +20,7 @@ #import "AppDelegate.h" #import "PhotoFeedViewController.h" #import "PhotoFeedNodeController.h" +#import "PhotoFeedListKitViewController.h" #import "WindowWithStatusBarUnderlay.h" #import "Utilities.h" @@ -37,13 +38,19 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( _window = [[WindowWithStatusBarUnderlay alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; _window.backgroundColor = [UIColor whiteColor]; - // UIKit Home Feed viewController & navController + // ASDK Home Feed viewController & navController PhotoFeedNodeController *asdkHomeFeedVC = [[PhotoFeedNodeController alloc] init]; UINavigationController *asdkHomeFeedNavCtrl = [[UINavigationController alloc] initWithRootViewController:asdkHomeFeedVC]; asdkHomeFeedNavCtrl.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"ASDK" image:[UIImage imageNamed:@"home"] tag:0]; asdkHomeFeedNavCtrl.hidesBarsOnSwipe = YES; - - // ASDK Home Feed viewController & navController + + // ListKit Home Feed viewController & navController + PhotoFeedListKitViewController *listKitHomeFeedVC = [[PhotoFeedListKitViewController alloc] init]; + UINavigationController *listKitHomeFeedNavCtrl = [[UINavigationController alloc] initWithRootViewController:listKitHomeFeedVC]; + listKitHomeFeedNavCtrl.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"ListKit" image:[UIImage imageNamed:@"home"] tag:0]; + listKitHomeFeedNavCtrl.hidesBarsOnSwipe = YES; + + // UIKit Home Feed viewController & navController PhotoFeedViewController *uikitHomeFeedVC = [[PhotoFeedViewController alloc] init]; UINavigationController *uikitHomeFeedNavCtrl = [[UINavigationController alloc] initWithRootViewController:uikitHomeFeedVC]; uikitHomeFeedNavCtrl.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"UIKit" image:[UIImage imageNamed:@"home"] tag:0]; @@ -51,7 +58,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // UITabBarController UITabBarController *tabBarController = [[UITabBarController alloc] init]; - tabBarController.viewControllers = @[uikitHomeFeedNavCtrl, asdkHomeFeedNavCtrl]; + tabBarController.viewControllers = @[uikitHomeFeedNavCtrl, asdkHomeFeedNavCtrl, listKitHomeFeedNavCtrl]; tabBarController.selectedViewController = asdkHomeFeedNavCtrl; tabBarController.delegate = self; [[UITabBar appearance] setTintColor:[UIColor darkBlueColor]]; diff --git a/examples/ASDKgram/Sample/CommentsNode.h b/examples/ASDKgram/Sample/CommentsNode.h index 3b0bbff424..2e8a9b2157 100644 --- a/examples/ASDKgram/Sample/CommentsNode.h +++ b/examples/ASDKgram/Sample/CommentsNode.h @@ -20,7 +20,7 @@ #import #import "CommentFeedModel.h" -@interface CommentsNode : ASTextCellNode +@interface CommentsNode : ASDisplayNode - (void)updateWithCommentFeedModel:(CommentFeedModel *)feed; diff --git a/examples/ASDKgram/Sample/CommentsNode.m b/examples/ASDKgram/Sample/CommentsNode.m index 67859bd37f..88d2cb78c1 100644 --- a/examples/ASDKgram/Sample/CommentsNode.m +++ b/examples/ASDKgram/Sample/CommentsNode.m @@ -96,6 +96,7 @@ - (void)createCommentLabels for (NSUInteger i = 0; i < numLabelsToAdd; i++) { ASTextNode *commentLabel = [[ASTextNode alloc] init]; + commentLabel.layerBacked = YES; commentLabel.maximumNumberOfLines = 3; [_commentNodes addObject:commentLabel]; diff --git a/examples/ASDKgram/Sample/FeedHeaderNode.h b/examples/ASDKgram/Sample/FeedHeaderNode.h new file mode 100644 index 0000000000..b440a05f52 --- /dev/null +++ b/examples/ASDKgram/Sample/FeedHeaderNode.h @@ -0,0 +1,13 @@ +// +// FeedHeaderNode.h +// Sample +// +// Created by Adlai Holler on 1/23/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +@interface FeedHeaderNode : ASCellNode + +@end diff --git a/examples/ASDKgram/Sample/FeedHeaderNode.m b/examples/ASDKgram/Sample/FeedHeaderNode.m new file mode 100644 index 0000000000..e0c8798aa2 --- /dev/null +++ b/examples/ASDKgram/Sample/FeedHeaderNode.m @@ -0,0 +1,35 @@ +// +// FeedHeaderNode.m +// Sample +// +// Created by Adlai Holler on 1/23/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "FeedHeaderNode.h" +#import "Utilities.h" + +static UIEdgeInsets kFeedHeaderInset = { .top = 20, .bottom = 20, .left = 10, .right = 10 }; + +@interface FeedHeaderNode () +@property (nonatomic, strong, readonly) ASTextNode *textNode; +@end + +@implementation FeedHeaderNode + +- (instancetype)init +{ + if (self = [super init]) { + _textNode = [[ASTextNode alloc] init]; + self.automaticallyManagesSubnodes = YES; + _textNode.attributedText = [NSAttributedString attributedStringWithString:@"Latest Posts" fontSize:18 color:[UIColor darkGrayColor] firstWordColor:nil]; + } + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:kFeedHeaderInset child:_textNode]; +} + +@end diff --git a/examples/ASDKgram/Sample/PhotoCellNode.m b/examples/ASDKgram/Sample/PhotoCellNode.m index 176cf4b608..a0b13fc04b 100644 --- a/examples/ASDKgram/Sample/PhotoCellNode.m +++ b/examples/ASDKgram/Sample/PhotoCellNode.m @@ -25,6 +25,10 @@ #import "PINImageView+PINRemoteImage.h" #import "PINButton+PINRemoteImage.h" +// There are many ways to format ASLayoutSpec code. In this example, we offer two different formats: +// A flatter, more ordinary Objective-C style; or a more structured, "visually" declarative style. +#define FLAT_LAYOUT 0 + #define DEBUG_PHOTOCELL_LAYOUT 0 #define HEADER_HEIGHT 50 @@ -33,12 +37,16 @@ #define VERTICAL_BUFFER 5 #define FONT_SIZE 14 +#define InsetForAvatar UIEdgeInsetsMake(HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER) +#define InsetForHeader UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER) +#define InsetForFooter UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER) + @implementation PhotoCellNode { PhotoModel *_photoModel; - CommentsNode *_photoCommentsView; - ASNetworkImageNode *_userAvatarImageView; - ASNetworkImageNode *_photoImageView; + CommentsNode *_photoCommentsNode; + ASNetworkImageNode *_userAvatarImageNode; + ASNetworkImageNode *_photoImageNode; ASTextNode *_userNameLabel; ASTextNode *_photoLocationLabel; ASTextNode *_photoTimeIntervalSincePostLabel; @@ -56,18 +64,18 @@ - (instancetype)initWithPhotoObject:(PhotoModel *)photo; _photoModel = photo; - _userAvatarImageView = [[ASNetworkImageNode alloc] init]; - _userAvatarImageView.URL = photo.ownerUserProfile.userPicURL; // FIXME: make round + _userAvatarImageNode = [[ASNetworkImageNode alloc] init]; + _userAvatarImageNode.URL = photo.ownerUserProfile.userPicURL; // FIXME: make round // FIXME: autocomplete for this line seems broken - [_userAvatarImageView setImageModificationBlock:^UIImage *(UIImage *image) { + [_userAvatarImageNode setImageModificationBlock:^UIImage *(UIImage *image) { CGSize profileImageSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); return [image makeCircularImageWithSize:profileImageSize]; }]; - _photoImageView = [[ASNetworkImageNode alloc] init]; - _photoImageView.URL = photo.URL; - _photoImageView.layerBacked = YES; + _photoImageNode = [[ASNetworkImageNode alloc] init]; + _photoImageNode.URL = photo.URL; + _photoImageNode.layerBacked = YES; _userNameLabel = [[ASTextNode alloc] init]; _userNameLabel.attributedText = [photo.ownerUserProfile usernameAttributedStringWithFontSize:FONT_SIZE]; @@ -90,19 +98,19 @@ - (instancetype)initWithPhotoObject:(PhotoModel *)photo; _photoDescriptionLabel = [self createLayerBackedTextNodeWithString:[photo descriptionAttributedStringWithFontSize:FONT_SIZE]]; _photoDescriptionLabel.maximumNumberOfLines = 3; - _photoCommentsView = [[CommentsNode alloc] init]; - // For now disable shouldRasterizeDescendants as it will throw an assertion: 'Node should always be marked invisible before deallocating. ...' - //_photoCommentsView.shouldRasterizeDescendants = YES; + _photoCommentsNode = [[CommentsNode alloc] init]; + + _photoCommentsNode.layerBacked = YES; // instead of adding everything addSubnode: self.automaticallyManagesSubnodes = YES; #if DEBUG_PHOTOCELL_LAYOUT - _userAvatarImageView.backgroundColor = [UIColor greenColor]; + _userAvatarImageNode.backgroundColor = [UIColor greenColor]; _userNameLabel.backgroundColor = [UIColor greenColor]; _photoLocationLabel.backgroundColor = [UIColor greenColor]; _photoTimeIntervalSincePostLabel.backgroundColor = [UIColor greenColor]; - _photoCommentsView.backgroundColor = [UIColor greenColor]; + _photoCommentsNode.backgroundColor = [UIColor greenColor]; _photoDescriptionLabel.backgroundColor = [UIColor greenColor]; _photoLikesLabel.backgroundColor = [UIColor greenColor]; #endif @@ -113,96 +121,156 @@ - (instancetype)initWithPhotoObject:(PhotoModel *)photo; - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { - return - // Main stack - [ASStackLayoutSpec - stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical - spacing:0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStretch - children:@[ - - // Header stack with inset - [ASInsetLayoutSpec - insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER) - child: - // Header stack - [ASStackLayoutSpec - stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal - spacing:0.0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsCenter - children:@[ - // Avatar image with inset - [ASInsetLayoutSpec - insetLayoutSpecWithInsets:UIEdgeInsetsMake(HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER) - child: - [_userAvatarImageView styledWithBlock:^(ASLayoutElementStyle *style) { - style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); - }] - ], - // User and photo location stack - [[ASStackLayoutSpec - stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical - spacing:0.0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStretch - children:_photoLocationLabel.attributedText ? @[ - [_userNameLabel styledWithBlock:^(ASLayoutElementStyle *style) { - style.flexShrink = 1.0; - }], - [_photoLocationLabel styledWithBlock:^(ASLayoutElementStyle *style) { - style.flexShrink = 1.0; - }] - ] : - @[ - [_userNameLabel styledWithBlock:^(ASLayoutElementStyle *style) { - style.flexShrink = 1.0; - }] - ]] - styledWithBlock:^(ASLayoutElementStyle *style) { - style.flexShrink = 1.0; - }], - // Spacer between user / photo location and photo time inverval - [[ASLayoutSpec new] styledWithBlock:^(ASLayoutElementStyle *style) { - style.flexGrow = 1.0; - }], - // Photo and time interval node - [_photoTimeIntervalSincePostLabel styledWithBlock:^(ASLayoutElementStyle *style) { - // to remove double spaces around spacer - style.spacingBefore = HORIZONTAL_BUFFER; - }] - ]] - ], + // There are many ways to format ASLayoutSpec code. In this example, we offer two different formats: + // A flatter, more ordinary Objective-C style; or a more structured, "visually" declarative style. + if (FLAT_LAYOUT) { + // This layout has a horizontal stack of header items at the top, set within a vertical stack of items. + NSMutableArray *headerChildren = [NSMutableArray array]; + NSMutableArray *verticalChildren = [NSMutableArray array]; + + // Header stack + ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; + headerStack.alignItems = ASStackLayoutAlignItemsCenter; + + // Avatar Image, with inset - first thing in the header stack. + _userAvatarImageNode.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); + [headerChildren addObject:[ASInsetLayoutSpec insetLayoutSpecWithInsets:InsetForAvatar child:_userAvatarImageNode]]; + + // User Name and Photo Location stack is next + ASStackLayoutSpec *userPhotoLocationStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + userPhotoLocationStack.style.flexShrink = 1.0; + [headerChildren addObject:userPhotoLocationStack]; - // Center photo with ratio - [ASRatioLayoutSpec - ratioLayoutSpecWithRatio:1.0 - child:_photoImageView], + // Setup the inside of the User Name and Photo Location stack. + _userNameLabel.style.flexShrink = 1.0; + [userPhotoLocationStack setChildren:@[_userNameLabel]]; + + if (_photoLocationLabel.attributedText) { + _photoLocationLabel.style.flexShrink = 1.0; + [userPhotoLocationStack setChildren:[userPhotoLocationStack.children arrayByAddingObject:_photoLocationLabel]]; + } + + // Add a spacer to allow a flexible space between the User Name / Location stack, and the Timestamp. + ASLayoutSpec *spacer = [ASLayoutSpec new]; + spacer.style.flexGrow = 1.0; + [headerChildren addObject:spacer]; - // Footer stack with inset - [ASInsetLayoutSpec - insetLayoutSpecWithInsets:UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER) - child: - [ASStackLayoutSpec - stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical - spacing:VERTICAL_BUFFER - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStretch - children:@[ - _photoLikesLabel, - _photoDescriptionLabel, - _photoCommentsView - ]] - ] - ]]; + // Photo Timestamp Label. + _photoTimeIntervalSincePostLabel.style.spacingBefore = HORIZONTAL_BUFFER; + [headerChildren addObject:_photoTimeIntervalSincePostLabel]; + + // Add all of the above items to the horizontal header stack + headerStack.children = headerChildren; + + // Create the last stack before assembling everything: the Footer Stack contains the description and comments. + ASStackLayoutSpec *footerStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + footerStack.spacing = VERTICAL_BUFFER; + footerStack.children = @[_photoLikesLabel, _photoDescriptionLabel, _photoCommentsNode]; + + // Main Vertical Stack: contains header, large main photo with fixed aspect ratio, and footer. + ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + + [verticalChildren addObject:[ASInsetLayoutSpec insetLayoutSpecWithInsets:InsetForHeader child:headerStack]]; + [verticalChildren addObject:[ASRatioLayoutSpec ratioLayoutSpecWithRatio :1.0 child:_photoImageNode]]; + [verticalChildren addObject:[ASInsetLayoutSpec insetLayoutSpecWithInsets:InsetForFooter child:footerStack]]; + + verticalStack.children = verticalChildren; + + return verticalStack; + + } else { // The style below is the more structured, visual, and declarative style. It is functionally identical. + + return + // Main stack + [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStretch + children:@[ + + // Header stack with inset + [ASInsetLayoutSpec + insetLayoutSpecWithInsets:InsetForHeader + child: + // Header stack + [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children:@[ + // Avatar image with inset + [ASInsetLayoutSpec + insetLayoutSpecWithInsets:InsetForAvatar + child: + [_userAvatarImageNode styledWithBlock:^(ASLayoutElementStyle *style) { + style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); + }] + ], + // User and photo location stack + [[ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStretch + children:_photoLocationLabel.attributedText ? @[ + [_userNameLabel styledWithBlock:^(ASLayoutElementStyle *style) { + style.flexShrink = 1.0; + }], + [_photoLocationLabel styledWithBlock:^(ASLayoutElementStyle *style) { + style.flexShrink = 1.0; + }] + ] : + @[ + [_userNameLabel styledWithBlock:^(ASLayoutElementStyle *style) { + style.flexShrink = 1.0; + }] + ]] + styledWithBlock:^(ASLayoutElementStyle *style) { + style.flexShrink = 1.0; + }], + // Spacer between user / photo location and photo time inverval + [[ASLayoutSpec new] styledWithBlock:^(ASLayoutElementStyle *style) { + style.flexGrow = 1.0; + }], + // Photo and time interval node + [_photoTimeIntervalSincePostLabel styledWithBlock:^(ASLayoutElementStyle *style) { + // to remove double spaces around spacer + style.spacingBefore = HORIZONTAL_BUFFER; + }] + ]] + ], + + // Center photo with ratio + [ASRatioLayoutSpec + ratioLayoutSpecWithRatio:1.0 + child:_photoImageNode], + + // Footer stack with inset + [ASInsetLayoutSpec + insetLayoutSpecWithInsets:InsetForFooter + child: + [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:VERTICAL_BUFFER + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStretch + children:@[ + _photoLikesLabel, + _photoDescriptionLabel, + _photoCommentsNode + ]] + ] + ]]; + } } #pragma mark - Instance Methods -- (void)fetchData +- (void)didEnterPreloadState { - [super fetchData]; + [super didEnterPreloadState]; [_photoModel.commentFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) { [self loadCommentsForPhoto:_photoModel]; @@ -222,7 +290,7 @@ - (ASTextNode *)createLayerBackedTextNodeWithString:(NSAttributedString *)attrib - (void)loadCommentsForPhoto:(PhotoModel *)photo { if (photo.commentFeed.numberOfItemsInFeed > 0) { - [_photoCommentsView updateWithCommentFeedModel:photo.commentFeed]; + [_photoCommentsNode updateWithCommentFeedModel:photo.commentFeed]; [self setNeedsLayout]; } diff --git a/examples/ASDKgram/Sample/PhotoFeedBaseController.h b/examples/ASDKgram/Sample/PhotoFeedBaseController.h new file mode 100644 index 0000000000..10029863c3 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedBaseController.h @@ -0,0 +1,39 @@ +// +// PhotoFeedBaseController.h +// Sample +// +// Created by Huy Nguyen on 20/12/16. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import +#import "PhotoFeedControllerProtocol.h" + +@protocol PhotoFeedControllerProtocol; +@class PhotoFeedModel; + +@interface PhotoFeedBaseController : ASViewController + +@property (nonatomic, strong, readonly) PhotoFeedModel *photoFeed; +@property (nonatomic, strong, readonly) UITableView *tableView; + +- (void)refreshFeed; +- (void)insertNewRows:(NSArray *)newPhotos; + +#pragma mark - Subclasses must override these methods + +- (void)loadPage; +- (void)requestCommentsForPhotos:(NSArray *)newPhotos; + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedBaseController.m b/examples/ASDKgram/Sample/PhotoFeedBaseController.m new file mode 100644 index 0000000000..c901d737e2 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedBaseController.m @@ -0,0 +1,123 @@ +// +// PhotoFeedBaseController.m +// Sample +// +// Created by Huy Nguyen on 20/12/16. +// Copyright © 2016 Facebook. All rights reserved. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "PhotoFeedBaseController.h" +#import "PhotoFeedModel.h" + +@implementation PhotoFeedBaseController +{ + UIActivityIndicatorView *_activityIndicatorView; +} + +// -loadView is guaranteed to be called on the main thread and is the appropriate place to +// set up an UIKit objects you may be using. +- (void)loadView +{ + [super loadView]; + + _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + + _photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:[self imageSizeForScreenWidth]]; + [self refreshFeed]; + + CGSize boundSize = self.view.bounds.size; + [_activityIndicatorView sizeToFit]; + CGRect refreshRect = _activityIndicatorView.frame; + refreshRect.origin = CGPointMake((boundSize.width - _activityIndicatorView.frame.size.width) / 2.0, + (boundSize.height - _activityIndicatorView.frame.size.height) / 2.0); + _activityIndicatorView.frame = refreshRect; + [self.view addSubview:_activityIndicatorView]; + + self.tableView.allowsSelection = NO; + self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + + self.view.backgroundColor = [UIColor whiteColor]; +} + +- (void)refreshFeed +{ + [_activityIndicatorView startAnimating]; + // small first batch + [_photoFeed refreshFeedWithCompletionBlock:^(NSArray *newPhotos){ + + [_activityIndicatorView stopAnimating]; + + [self insertNewRows:newPhotos]; + [self requestCommentsForPhotos:newPhotos]; + + // immediately start second larger fetch + [self loadPage]; + + } numResultsToReturn:4]; +} + +- (void)insertNewRows:(NSArray *)newPhotos +{ + NSInteger section = 0; + NSMutableArray *indexPaths = [NSMutableArray array]; + + NSInteger newTotalNumberOfPhotos = [_photoFeed numberOfItemsInFeed]; + for (NSInteger row = newTotalNumberOfPhotos - newPhotos.count; row < newTotalNumberOfPhotos; row++) { + NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section]; + [indexPaths addObject:path]; + } + [self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle +{ + return UIStatusBarStyleLightContent; +} + +- (CGSize)imageSizeForScreenWidth +{ + CGRect screenRect = [[UIScreen mainScreen] bounds]; + CGFloat screenScale = [[UIScreen mainScreen] scale]; + return CGSizeMake(screenRect.size.width * screenScale, screenRect.size.width * screenScale); +} + +#pragma mark - PhotoFeedViewControllerProtocol + +- (void)resetAllData +{ + [_photoFeed clearFeed]; + [self.tableView reloadData]; + [self refreshFeed]; +} + +#pragma mark - Subclassing + +- (UITableView *)tableView +{ + NSAssert(NO, @"Subclasses must override this method"); + return nil; +} + +- (void)loadPage +{ + NSAssert(NO, @"Subclasses must override this method"); +} + +- (void)requestCommentsForPhotos:(NSArray *)newPhotos +{ + NSAssert(NO, @"Subclasses must override this method"); +} + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedControllerProtocol.h b/examples/ASDKgram/Sample/PhotoFeedControllerProtocol.h new file mode 100644 index 0000000000..e7c5be3689 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedControllerProtocol.h @@ -0,0 +1,13 @@ +// +// PhotoFeedControllerProtocol.h +// Sample +// +// Created by Michael Schneider on 2/12/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +@protocol PhotoFeedControllerProtocol +- (void)resetAllData; +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedListKitViewController.h b/examples/ASDKgram/Sample/PhotoFeedListKitViewController.h new file mode 100644 index 0000000000..0720363a4c --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedListKitViewController.h @@ -0,0 +1,14 @@ +// +// PhotoFeedListKitViewController.h +// Sample +// +// Created by Adlai Holler on 12/29/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import "PhotoFeedControllerProtocol.h" + +@interface PhotoFeedListKitViewController : ASViewController + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedListKitViewController.m b/examples/ASDKgram/Sample/PhotoFeedListKitViewController.m new file mode 100644 index 0000000000..5aa50732b7 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedListKitViewController.m @@ -0,0 +1,106 @@ +// +// PhotoFeedListKitViewController.m +// Sample +// +// Created by Adlai Holler on 12/29/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "PhotoFeedListKitViewController.h" +#import +#import "PhotoFeedModel.h" +#import "PhotoFeedSectionController.h" +#import "RefreshingSectionControllerType.h" + +@interface PhotoFeedListKitViewController () +@property (nonatomic, strong) IGListAdapter *listAdapter; +@property (nonatomic, strong) PhotoFeedModel *photoFeed; +@property (nonatomic, strong, readonly) ASCollectionNode *collectionNode; +@property (nonatomic, strong, readonly) UIActivityIndicatorView *spinner; +@property (nonatomic, strong, readonly) UIRefreshControl *refreshCtrl; +@end + +@implementation PhotoFeedListKitViewController +@synthesize spinner = _spinner; + +- (instancetype)init +{ + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + ASCollectionNode *node = [[ASCollectionNode alloc] initWithCollectionViewLayout:layout]; + if (self = [super initWithNode:node]) { + CGRect screenRect = [[UIScreen mainScreen] bounds]; + CGFloat screenScale = [[UIScreen mainScreen] scale]; + CGSize screenWidthImageSize = CGSizeMake(screenRect.size.width * screenScale, screenRect.size.width * screenScale); + _photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:screenWidthImageSize]; + + IGListAdapterUpdater *updater = [[IGListAdapterUpdater alloc] init]; + _listAdapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:self workingRangeSize:0]; + _listAdapter.dataSource = self; + [_listAdapter setASDKCollectionNode:self.collectionNode]; + } + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + self.collectionNode.view.alwaysBounceVertical = YES; + _refreshCtrl = [[UIRefreshControl alloc] init]; + [_refreshCtrl addTarget:self action:@selector(refreshFeed) forControlEvents:UIControlEventValueChanged]; + [self.collectionNode.view addSubview:_refreshCtrl]; + _spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; +} + +- (ASCollectionNode *)collectionNode +{ + return self.node; +} + +- (void)resetAllData +{ + // nop, not used currently +} + +- (void)refreshFeed +{ + // Ask the first section controller to do the refreshing. + id secCtrl = [self.listAdapter sectionControllerForObject:self.photoFeed]; + if ([secCtrl conformsToProtocol:@protocol(RefreshingSectionControllerType)]) { + [secCtrl refreshContentWithCompletion:^{ + [self.refreshCtrl endRefreshing]; + }]; + } +} + +- (UIActivityIndicatorView *)spinner +{ + if (_spinner == nil) { + _spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + [_spinner startAnimating]; + } + return _spinner; +} + +#pragma mark - IGListAdapterDataSource + +- (NSArray> *)objectsForListAdapter:(IGListAdapter *)listAdapter +{ + return @[ self.photoFeed ]; +} + +- (UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter +{ + return self.spinner; +} + +- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object +{ + if ([object isKindOfClass:[PhotoFeedModel class]]) { + return [[PhotoFeedSectionController alloc] init]; + } else { + ASDisplayNodeFailAssert(@"Only supports objects of class PhotoFeedModel."); + return nil; + } +} + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedModel.h b/examples/ASDKgram/Sample/PhotoFeedModel.h index b0d937013b..0690cfb10a 100644 --- a/examples/ASDKgram/Sample/PhotoFeedModel.h +++ b/examples/ASDKgram/Sample/PhotoFeedModel.h @@ -18,6 +18,7 @@ // #import "PhotoModel.h" +#import typedef NS_ENUM(NSInteger, PhotoFeedModelType) { PhotoFeedModelTypePopular, @@ -25,11 +26,13 @@ typedef NS_ENUM(NSInteger, PhotoFeedModelType) { PhotoFeedModelTypeUserPhotos }; -@interface PhotoFeedModel : NSObject +@interface PhotoFeedModel : NSObject - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithPhotoFeedModelType:(PhotoFeedModelType)type imageSize:(CGSize)size NS_DESIGNATED_INITIALIZER; +@property (nonatomic, readonly) NSArray *photos; + - (NSUInteger)totalNumberOfPhotos; - (NSUInteger)numberOfItemsInFeed; - (PhotoModel *)objectAtIndex:(NSUInteger)index; diff --git a/examples/ASDKgram/Sample/PhotoFeedModel.m b/examples/ASDKgram/Sample/PhotoFeedModel.m index 7fb661205f..27cf185e9d 100644 --- a/examples/ASDKgram/Sample/PhotoFeedModel.m +++ b/examples/ASDKgram/Sample/PhotoFeedModel.m @@ -47,13 +47,6 @@ @implementation PhotoFeedModel NSUInteger _userID; } -#pragma mark - Properties - -- (NSMutableArray *)photos -{ - return _photos; -} - #pragma mark - Lifecycle - (instancetype)initWithPhotoFeedModelType:(PhotoFeedModelType)type imageSize:(CGSize)size @@ -92,6 +85,11 @@ - (instancetype)initWithPhotoFeedModelType:(PhotoFeedModelType)type imageSize:(C #pragma mark - Instance Methods +- (NSArray *)photos +{ + return [_photos copy]; +} + - (NSUInteger)totalNumberOfPhotos { return _totalItems; @@ -186,10 +184,13 @@ - (void)fetchPageWithCompletionBlock:(void (^)(NSArray *))block numResultsToRetu // early return if reached end of pages if (_totalPages) { if (_currentPage == _totalPages) { + if (block){ + block(@[]); + } return; } } - + NSUInteger numPhotos = (numResults < 100) ? numResults : 100; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -246,4 +247,16 @@ - (void)fetchPageWithCompletionBlock:(void (^)(NSArray *))block numResultsToRetu }); } +#pragma mark - IGListDiffable + +- (id)diffIdentifier +{ + return self; +} + +- (BOOL)isEqualToDiffableObject:(id)object +{ + return self == object; +} + @end diff --git a/examples/ASDKgram/Sample/PhotoFeedNodeController.h b/examples/ASDKgram/Sample/PhotoFeedNodeController.h index 04c9aa896c..c97576b7bc 100644 --- a/examples/ASDKgram/Sample/PhotoFeedNodeController.h +++ b/examples/ASDKgram/Sample/PhotoFeedNodeController.h @@ -17,9 +17,8 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#import -#import "AppDelegate.h" +#import "PhotoFeedBaseController.h" -@interface PhotoFeedNodeController : ASViewController +@interface PhotoFeedNodeController : PhotoFeedBaseController @end diff --git a/examples/ASDKgram/Sample/PhotoFeedNodeController.m b/examples/ASDKgram/Sample/PhotoFeedNodeController.m index 322c9de49a..1e22925e6d 100644 --- a/examples/ASDKgram/Sample/PhotoFeedNodeController.m +++ b/examples/ASDKgram/Sample/PhotoFeedNodeController.m @@ -31,9 +31,7 @@ @interface PhotoFeedNodeController () @implementation PhotoFeedNodeController { - PhotoFeedModel *_photoFeed; - ASTableNode *_tableNode; - UIActivityIndicatorView *_activityIndicatorView; + ASTableNode *_tableNode; } #pragma mark - Lifecycle @@ -51,7 +49,6 @@ - (instancetype)init _tableNode.dataSource = self; _tableNode.delegate = self; - } return self; @@ -63,112 +60,48 @@ - (void)loadView { [super loadView]; - _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; - - _photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:[self imageSizeForScreenWidth]]; - [self refreshFeed]; - - CGSize boundSize = self.view.bounds.size; - - [_activityIndicatorView sizeToFit]; - CGRect refreshRect = _activityIndicatorView.frame; - refreshRect.origin = CGPointMake((boundSize.width - _activityIndicatorView.frame.size.width) / 2.0, - (boundSize.height - _activityIndicatorView.frame.size.height) / 2.0); - _activityIndicatorView.frame = refreshRect; - - [self.view addSubview:_activityIndicatorView]; - - self.view.backgroundColor = [UIColor whiteColor]; - _tableNode.view.allowsSelection = NO; - _tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; _tableNode.view.leadingScreensForBatching = AUTO_TAIL_LOADING_NUM_SCREENFULS; // overriding default of 2.0 } -#pragma mark - helper methods - -- (void)refreshFeed -{ - [_activityIndicatorView startAnimating]; - // small first batch - [_photoFeed refreshFeedWithCompletionBlock:^(NSArray *newPhotos){ - - [_activityIndicatorView stopAnimating]; - - [self insertNewRowsInTableNode:newPhotos]; -// [self requestCommentsForPhotos:newPhotos]; - - // immediately start second larger fetch - [self loadPageWithContext:nil]; - - } numResultsToReturn:4]; -} - - (void)loadPageWithContext:(ASBatchContext *)context { - [_photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){ + [self.photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){ - [self insertNewRowsInTableNode:newPhotos]; -// [self requestCommentsForPhotos:newPhotos]; + [self insertNewRows:newPhotos]; + [self requestCommentsForPhotos:newPhotos]; if (context) { [context completeBatchFetching:YES]; } } numResultsToReturn:20]; } -//- (void)requestCommentsForPhotos:(NSArray *)newPhotos -//{ -// for (PhotoModel *photo in newPhotos) { -// [photo.commentFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) { -// -// NSInteger rowNum = [_photoFeed indexOfPhotoModel:photo]; -// NSIndexPath *cellPath = [NSIndexPath indexPathForRow:rowNum inSection:0]; -// PhotoCellNode *cell = (PhotoCellNode *)[_tableNode.view nodeForRowAtIndexPath:cellPath]; -// -// if (cell) { -// [cell loadCommentsForPhoto:photo]; -// [_tableNode.view beginUpdates]; -// [_tableNode.view endUpdates]; -// } -// }]; -// } -//} - -- (void)insertNewRowsInTableNode:(NSArray *)newPhotos +#pragma mark - Subclassing + +- (UITableView *)tableView { - NSInteger section = 0; - NSMutableArray *indexPaths = [NSMutableArray array]; - - NSUInteger newTotalNumberOfPhotos = [_photoFeed numberOfItemsInFeed]; - for (NSUInteger row = newTotalNumberOfPhotos - newPhotos.count; row < newTotalNumberOfPhotos; row++) { - NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section]; - [indexPaths addObject:path]; - } - - [_tableNode insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; + return _tableNode.view; } -- (UIStatusBarStyle)preferredStatusBarStyle +- (void)loadPage { - return UIStatusBarStyleLightContent; + [self loadPageWithContext:nil]; } -- (CGSize)imageSizeForScreenWidth +- (void)requestCommentsForPhotos:(NSArray *)newPhotos { - CGRect screenRect = [[UIScreen mainScreen] bounds]; - CGFloat screenScale = [[UIScreen mainScreen] scale]; - return CGSizeMake(screenRect.size.width * screenScale, screenRect.size.width * screenScale); + // Do nothing (#1530). } #pragma mark - ASTableDataSource methods - (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section { - return [_photoFeed numberOfItemsInFeed]; + return [self.photoFeed numberOfItemsInFeed]; } - (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath { - PhotoModel *photoModel = [_photoFeed objectAtIndex:indexPath.row]; + PhotoModel *photoModel = [self.photoFeed objectAtIndex:indexPath.row]; // this will be executed on a background thread - important to make sure it's thread safe ASCellNode *(^ASCellNodeBlock)() = ^ASCellNode *() { PhotoCellNode *cellNode = [[PhotoCellNode alloc] initWithPhotoObject:photoModel]; @@ -187,13 +120,4 @@ - (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBat [self loadPageWithContext:context]; } -#pragma mark - PhotoFeedViewControllerProtocol - -- (void)resetAllData -{ - [_photoFeed clearFeed]; - [_tableNode reloadData]; - [self refreshFeed]; -} - @end diff --git a/examples/ASDKgram/Sample/PhotoFeedSectionController.h b/examples/ASDKgram/Sample/PhotoFeedSectionController.h new file mode 100644 index 0000000000..37ca7a8f92 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedSectionController.h @@ -0,0 +1,24 @@ +// +// PhotoFeedSectionController.h +// Sample +// +// Created by Adlai Holler on 12/29/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import +#import "RefreshingSectionControllerType.h" +#import "ASCollectionSectionController.h" + +@class PhotoFeedModel; + +NS_ASSUME_NONNULL_BEGIN + +@interface PhotoFeedSectionController : ASCollectionSectionController + +@property (nonatomic, strong, nullable) PhotoFeedModel *photoFeed; + +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/ASDKgram/Sample/PhotoFeedSectionController.m b/examples/ASDKgram/Sample/PhotoFeedSectionController.m new file mode 100644 index 0000000000..190a817853 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedSectionController.m @@ -0,0 +1,139 @@ +// +// PhotoFeedSectionController.m +// Sample +// +// Created by Adlai Holler on 12/29/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "PhotoFeedSectionController.h" +#import "PhotoFeedModel.h" +#import "PhotoModel.h" +#import "PhotoCellNode.h" +#import "TailLoadingNode.h" +#import "FeedHeaderNode.h" + +@interface PhotoFeedSectionController () +@property (nonatomic, strong) NSString *paginatingSpinner; +@end + +@implementation PhotoFeedSectionController + +- (instancetype)init +{ + if (self = [super init]) { + _paginatingSpinner = @"Paginating Spinner"; + self.supplementaryViewSource = self; + } + return self; +} + +#pragma mark - IGListSectionType + +- (void)didUpdateToObject:(id)object +{ + _photoFeed = object; + [self setItems:_photoFeed.photos animated:NO completion:nil]; +} + +- (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index +{ + return [ASIGListSectionControllerMethods cellForItemAtIndex:index sectionController:self]; +} + +- (CGSize)sizeForItemAtIndex:(NSInteger)index +{ + return [ASIGListSectionControllerMethods sizeForItemAtIndex:index]; +} + +- (void)didSelectItemAtIndex:(NSInteger)index +{ + // nop +} + +#pragma mark - ASSectionController + +- (ASCellNodeBlock)nodeBlockForItemAtIndex:(NSInteger)index +{ + id object = self.items[index]; + // this will be executed on a background thread - important to make sure it's thread safe + ASCellNode *(^nodeBlock)() = nil; + if (object == _paginatingSpinner) { + nodeBlock = ^{ + return [[TailLoadingNode alloc] init]; + }; + } else if ([object isKindOfClass:[PhotoModel class]]) { + PhotoModel *photoModel = object; + nodeBlock = ^{ + PhotoCellNode *cellNode = [[PhotoCellNode alloc] initWithPhotoObject:photoModel]; + return cellNode; + }; + } + + return nodeBlock; +} + +- (void)beginBatchFetchWithContext:(ASBatchContext *)context +{ + dispatch_async(dispatch_get_main_queue(), ^{ + // Immediately add the loading spinner if needed. + if (self.items.count > 0) { + NSArray *newItems = [self.items arrayByAddingObject:_paginatingSpinner]; + [self setItems:newItems animated:NO completion:nil]; + } + + // Start the fetch, then update the items (removing the spinner) when they are loaded. + [_photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){ + [self setItems:_photoFeed.photos animated:NO completion:^{ + [context completeBatchFetching:YES]; + }]; + } numResultsToReturn:20]; + }); +} + +#pragma mark - RefreshingSectionControllerType + +- (void)refreshContentWithCompletion:(void(^)())completion +{ + [_photoFeed refreshFeedWithCompletionBlock:^(NSArray *addedItems) { + [self setItems:_photoFeed.photos animated:YES completion:completion]; + } numResultsToReturn:4]; +} + +#pragma mark - ASSupplementaryNodeSource + +- (ASCellNodeBlock)nodeBlockForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index +{ + ASDisplayNodeAssert([elementKind isEqualToString:UICollectionElementKindSectionHeader], nil); + return ^{ + return [[FeedHeaderNode alloc] init]; + }; +} + +- (ASSizeRange)sizeRangeForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index +{ + if ([elementKind isEqualToString:UICollectionElementKindSectionHeader]) { + return ASSizeRangeUnconstrained; + } else { + return ASSizeRangeZero; + } +} + +#pragma mark - IGListSupplementaryViewSource + +- (NSArray *)supportedElementKinds +{ + return @[ UICollectionElementKindSectionHeader ]; +} + +- (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index +{ + return [ASIGListSupplementaryViewSourceMethods viewForSupplementaryElementOfKind:elementKind atIndex:index sectionController:self]; +} + +- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index +{ + return [ASIGListSupplementaryViewSourceMethods sizeForSupplementaryViewOfKind:elementKind atIndex:index]; +} + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedViewController.h b/examples/ASDKgram/Sample/PhotoFeedViewController.h index 3942ec5c27..3733fde82b 100644 --- a/examples/ASDKgram/Sample/PhotoFeedViewController.h +++ b/examples/ASDKgram/Sample/PhotoFeedViewController.h @@ -17,8 +17,8 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#import "AppDelegate.h" +#import "PhotoFeedBaseController.h" -@interface PhotoFeedViewController : UIViewController +@interface PhotoFeedViewController : PhotoFeedBaseController @end diff --git a/examples/ASDKgram/Sample/PhotoFeedViewController.m b/examples/ASDKgram/Sample/PhotoFeedViewController.m index ddaf2a7cf9..3a36861e96 100644 --- a/examples/ASDKgram/Sample/PhotoFeedViewController.m +++ b/examples/ASDKgram/Sample/PhotoFeedViewController.m @@ -30,9 +30,7 @@ @interface PhotoFeedViewController () -@interface PhotoModel : NSObject +@interface PhotoModel : NSObject @property (nonatomic, strong, readonly) NSURL *URL; @property (nonatomic, strong, readonly) NSString *photoID; diff --git a/examples/ASDKgram/Sample/PhotoModel.m b/examples/ASDKgram/Sample/PhotoModel.m index db424b8232..575a7049a4 100644 --- a/examples/ASDKgram/Sample/PhotoModel.m +++ b/examples/ASDKgram/Sample/PhotoModel.m @@ -83,11 +83,7 @@ - (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size - (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size { - NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; - [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; - NSString *formattedLikesNumber = [formatter stringFromNumber:[[NSNumber alloc] initWithUnsignedInteger:self.likesCount]]; - - NSString *likesString = [NSString stringWithFormat:@"♥︎ %@ likes", formattedLikesNumber]; + NSString *likesString = [NSString stringWithFormat:@"♥︎ %lu likes", (unsigned long)_likesCount]; return [NSAttributedString attributedStringWithString:likesString fontSize:size color:[UIColor darkBlueColor] firstWordColor:nil]; } @@ -102,4 +98,14 @@ - (NSString *)description return [NSString stringWithFormat:@"%@ - %@", _photoID, _descriptionText]; } -@end \ No newline at end of file +- (id)diffIdentifier +{ + return self.photoID; +} + +- (BOOL)isEqualToDiffableObject:(id)object +{ + return [self isEqual:object]; +} + +@end diff --git a/examples/ASDKgram/Sample/RefreshingSectionControllerType.h b/examples/ASDKgram/Sample/RefreshingSectionControllerType.h new file mode 100644 index 0000000000..d2ed2ee712 --- /dev/null +++ b/examples/ASDKgram/Sample/RefreshingSectionControllerType.h @@ -0,0 +1,19 @@ +// +// RefreshingSectionControllerType.h +// Sample +// +// Created by Adlai Holler on 12/29/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol RefreshingSectionControllerType + +- (void)refreshContentWithCompletion:(nullable void(^)())completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/ASDKgram/Sample/TailLoadingNode.h b/examples/ASDKgram/Sample/TailLoadingNode.h new file mode 100644 index 0000000000..177a938e10 --- /dev/null +++ b/examples/ASDKgram/Sample/TailLoadingNode.h @@ -0,0 +1,17 @@ +// +// TailLoadingNode.h +// Sample +// +// Created by Adlai Holler on 1/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +/** + * A node that shows a UIActivityIndicatorView, useful for putting at the end of a + * list while the next page is loading. + */ +@interface TailLoadingNode : ASCellNode + +@end diff --git a/examples/ASDKgram/Sample/TailLoadingNode.m b/examples/ASDKgram/Sample/TailLoadingNode.m new file mode 100644 index 0000000000..9f096db706 --- /dev/null +++ b/examples/ASDKgram/Sample/TailLoadingNode.m @@ -0,0 +1,35 @@ +// +// TailLoadingNode.m +// Sample +// +// Created by Adlai Holler on 1/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "TailLoadingNode.h" + +@interface TailLoadingNode () +@property (nonatomic, strong) ASDisplayNode *activityIndicatorNode; +@end + +@implementation TailLoadingNode + +- (instancetype)init +{ + if (self = [super init]) { + _activityIndicatorNode = [[ASDisplayNode alloc] initWithViewBlock:^{ + UIActivityIndicatorView *v = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + [v startAnimating]; + return v; + }]; + self.style.height = ASDimensionMake(100); + } + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + return [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionMinimumXY child:self.activityIndicatorNode]; +} + +@end diff --git a/examples/ASMapNode/Podfile b/examples/ASMapNode/Podfile index 5c30ce798e..7a8d8c1a00 100644 --- a/examples/ASMapNode/Podfile +++ b/examples/ASMapNode/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj index 8dcd8bfdb7..529b587837 100644 --- a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj +++ b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj @@ -288,7 +288,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -325,7 +325,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/ASMapNode/Sample/MapHandlerNode.m b/examples/ASMapNode/Sample/MapHandlerNode.m index 33e09a5b69..3d7173783b 100644 --- a/examples/ASMapNode/Sample/MapHandlerNode.m +++ b/examples/ASMapNode/Sample/MapHandlerNode.m @@ -65,18 +65,18 @@ - (instancetype)init borderColor:[UIColor lightGrayColor] borderWidth:2.0]; - [_updateRegionButton setBackgroundImage:backgroundImage forState:ASControlStateNormal]; - [_updateRegionButton setBackgroundImage:backgroundHiglightedImage forState:ASControlStateHighlighted]; + [_updateRegionButton setBackgroundImage:backgroundImage forState:UIControlStateNormal]; + [_updateRegionButton setBackgroundImage:backgroundHiglightedImage forState:UIControlStateHighlighted]; - [_liveMapToggleButton setBackgroundImage:backgroundImage forState:ASControlStateNormal]; - [_liveMapToggleButton setBackgroundImage:backgroundHiglightedImage forState:ASControlStateHighlighted]; + [_liveMapToggleButton setBackgroundImage:backgroundImage forState:UIControlStateNormal]; + [_liveMapToggleButton setBackgroundImage:backgroundHiglightedImage forState:UIControlStateHighlighted]; _updateRegionButton.contentEdgeInsets = UIEdgeInsetsMake(5, 5, 5, 5); - [_updateRegionButton setTitle:@"Update Region" withFont:nil withColor:[UIColor blueColor] forState:ASControlStateNormal]; + [_updateRegionButton setTitle:@"Update Region" withFont:nil withColor:[UIColor blueColor] forState:UIControlStateNormal]; [_updateRegionButton addTarget:self action:@selector(updateRegion) forControlEvents:ASControlNodeEventTouchUpInside]; - [_liveMapToggleButton setTitle:[self liveMapStr] withFont:nil withColor:[UIColor blueColor] forState:ASControlStateNormal]; + [_liveMapToggleButton setTitle:[self liveMapStr] withFont:nil withColor:[UIColor blueColor] forState:UIControlStateNormal]; [_liveMapToggleButton addTarget:self action:@selector(toggleLiveMap) forControlEvents:ASControlNodeEventTouchUpInside]; @@ -214,8 +214,8 @@ - (void)toggleLiveMap { _mapNode.liveMap = !_mapNode.liveMap; NSString * const liveMapStr = [self liveMapStr]; - [_liveMapToggleButton setTitle:liveMapStr withFont:nil withColor:[UIColor blueColor] forState:ASControlStateNormal]; - [_liveMapToggleButton setTitle:liveMapStr withFont:[UIFont systemFontOfSize:14] withColor:[UIColor blueColor] forState:ASControlStateHighlighted]; + [_liveMapToggleButton setTitle:liveMapStr withFont:nil withColor:[UIColor blueColor] forState:UIControlStateNormal]; + [_liveMapToggleButton setTitle:liveMapStr withFont:[UIFont systemFontOfSize:14] withColor:[UIColor blueColor] forState:UIControlStateHighlighted]; } - (void)updateLocationTextWithMKCoordinateRegion:(MKCoordinateRegion)region diff --git a/examples/ASViewController/Podfile b/examples/ASViewController/Podfile index 5c30ce798e..7a8d8c1a00 100644 --- a/examples/ASViewController/Podfile +++ b/examples/ASViewController/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/ASViewController/Sample.xcodeproj/project.pbxproj b/examples/ASViewController/Sample.xcodeproj/project.pbxproj index eeef9f4c40..5343c32679 100644 --- a/examples/ASViewController/Sample.xcodeproj/project.pbxproj +++ b/examples/ASViewController/Sample.xcodeproj/project.pbxproj @@ -1,833 +1,389 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 06EE2E0ABEB6289D4775A867 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - 0DFDB4376BA084DAC7C1976E - - children - - 15AD337503831C4D33FF8B3A - 97482F27BE2F7583EFE1BC2C - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 15AD337503831C4D33FF8B3A - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - 23FC03B282CBD9014D868DF6 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - 465082D55CCF1B0CB1AEBACC - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - 478C8D7C412DCBDFE14640D8 - - children - - 465082D55CCF1B0CB1AEBACC - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - 5CF3EF5E344946731D4F13F2 - - fileRef - 465082D55CCF1B0CB1AEBACC - isa - PBXBuildFile - - 694993C41C8B334F00491CA5 - - children - - 694993CF1C8B334F00491CA5 - 694993CE1C8B334F00491CA5 - 0DFDB4376BA084DAC7C1976E - 478C8D7C412DCBDFE14640D8 - - isa - PBXGroup - sourceTree - <group> - - 694993C51C8B334F00491CA5 - - attributes - - LastUpgradeCheck - 0720 - ORGANIZATIONNAME - AsyncDisplayKit - TargetAttributes - - 694993CC1C8B334F00491CA5 - - CreatedOnToolsVersion - 7.2.1 - - - - buildConfigurationList - 694993C81C8B334F00491CA5 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - 694993C41C8B334F00491CA5 - productRefGroup - 694993CE1C8B334F00491CA5 - projectDirPath - - projectReferences - - projectRoot - - targets - - 694993CC1C8B334F00491CA5 - - - 694993C81C8B334F00491CA5 - - buildConfigurations - - 694993E21C8B334F00491CA5 - 694993E31C8B334F00491CA5 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 694993C91C8B334F00491CA5 - - buildActionMask - 2147483647 - files - - 694993D81C8B334F00491CA5 - 694993D51C8B334F00491CA5 - 694993D21C8B334F00491CA5 - 69DCA5221C8B3D30006FF548 - 69DCA5281C8BE031006FF548 - 69DCA5251C8BE01F006FF548 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 694993CA1C8B334F00491CA5 - - buildActionMask - 2147483647 - files - - 5CF3EF5E344946731D4F13F2 - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 694993CB1C8B334F00491CA5 - - buildActionMask - 2147483647 - files - - 694993E01C8B334F00491CA5 - 694993DD1C8B334F00491CA5 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 694993CC1C8B334F00491CA5 - - buildConfigurationList - 694993E41C8B334F00491CA5 - buildPhases - - 80035273449C25F4B2E1454F - 694993C91C8B334F00491CA5 - 694993CA1C8B334F00491CA5 - 694993CB1C8B334F00491CA5 - 06EE2E0ABEB6289D4775A867 - 23FC03B282CBD9014D868DF6 - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - 694993CD1C8B334F00491CA5 - productType - com.apple.product-type.application - - 694993CD1C8B334F00491CA5 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - 694993CE1C8B334F00491CA5 - - children - - 694993CD1C8B334F00491CA5 - - isa - PBXGroup - name - Products - sourceTree - <group> - - 694993CF1C8B334F00491CA5 - - children - - 694993D31C8B334F00491CA5 - 694993D41C8B334F00491CA5 - 694993D61C8B334F00491CA5 - 694993D71C8B334F00491CA5 - 69DCA5201C8B3D30006FF548 - 69DCA5211C8B3D30006FF548 - 69DCA5231C8BE01F006FF548 - 69DCA5241C8BE01F006FF548 - 69DCA5261C8BE031006FF548 - 69DCA5271C8BE031006FF548 - 694993DC1C8B334F00491CA5 - 694993D01C8B334F00491CA5 - - isa - PBXGroup - path - Sample - sourceTree - <group> - - 694993D01C8B334F00491CA5 - - children - - 694993E11C8B334F00491CA5 - 694993DE1C8B334F00491CA5 - 694993D11C8B334F00491CA5 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - 694993D11C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - 694993D21C8B334F00491CA5 - - fileRef - 694993D11C8B334F00491CA5 - isa - PBXBuildFile - - 694993D31C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - 694993D41C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - 694993D51C8B334F00491CA5 - - fileRef - 694993D41C8B334F00491CA5 - isa - PBXBuildFile - - 694993D61C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - 694993D71C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - 694993D81C8B334F00491CA5 - - fileRef - 694993D71C8B334F00491CA5 - isa - PBXBuildFile - - 694993DC1C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - folder.assetcatalog - path - Assets.xcassets - sourceTree - <group> - - 694993DD1C8B334F00491CA5 - - fileRef - 694993DC1C8B334F00491CA5 - isa - PBXBuildFile - - 694993DE1C8B334F00491CA5 - - children - - 694993DF1C8B334F00491CA5 - - isa - PBXVariantGroup - name - LaunchScreen.storyboard - sourceTree - <group> - - 694993DF1C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - file.storyboard - name - Base - path - Base.lproj/LaunchScreen.storyboard - sourceTree - <group> - - 694993E01C8B334F00491CA5 - - fileRef - 694993DE1C8B334F00491CA5 - isa - PBXBuildFile - - 694993E11C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - 694993E21C8B334F00491CA5 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - DEBUG_INFORMATION_FORMAT - dwarf - ENABLE_STRICT_OBJC_MSGSEND - YES - ENABLE_TESTABILITY - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_NO_COMMON_BLOCKS - YES - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 7.0 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - - isa - XCBuildConfiguration - name - Debug - - 694993E31C8B334F00491CA5 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - DEBUG_INFORMATION_FORMAT - dwarf-with-dsym - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_NO_COMMON_BLOCKS - YES - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 7.0 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - 694993E41C8B334F00491CA5 - - buildConfigurations - - 694993E51C8B334F00491CA5 - 694993E61C8B334F00491CA5 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 694993E51C8B334F00491CA5 - - baseConfigurationReference - 15AD337503831C4D33FF8B3A - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_BUNDLE_IDENTIFIER - org.AsyncDisplayKit.Sample - PRODUCT_NAME - $(TARGET_NAME) - - isa - XCBuildConfiguration - name - Debug - - 694993E61C8B334F00491CA5 - - baseConfigurationReference - 97482F27BE2F7583EFE1BC2C - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_BUNDLE_IDENTIFIER - org.AsyncDisplayKit.Sample - PRODUCT_NAME - $(TARGET_NAME) - - isa - XCBuildConfiguration - name - Release - - 69DCA5201C8B3D30006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - DetailViewController.h - sourceTree - <group> - - 69DCA5211C8B3D30006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - DetailViewController.m - sourceTree - <group> - - 69DCA5221C8B3D30006FF548 - - fileRef - 69DCA5211C8B3D30006FF548 - isa - PBXBuildFile - - 69DCA5231C8BE01F006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - DetailRootNode.h - sourceTree - <group> - - 69DCA5241C8BE01F006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - DetailRootNode.m - sourceTree - <group> - - 69DCA5251C8BE01F006FF548 - - fileRef - 69DCA5241C8BE01F006FF548 - isa - PBXBuildFile - - 69DCA5261C8BE031006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - DetailCellNode.h - sourceTree - <group> - - 69DCA5271C8BE031006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - DetailCellNode.m - sourceTree - <group> - - 69DCA5281C8BE031006FF548 - - fileRef - 69DCA5271C8BE031006FF548 - isa - PBXBuildFile - - 80035273449C25F4B2E1454F - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - 97482F27BE2F7583EFE1BC2C - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - - rootObject - 694993C51C8B334F00491CA5 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 5CF3EF5E344946731D4F13F2 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */; }; + 694993D21C8B334F00491CA5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D11C8B334F00491CA5 /* main.m */; }; + 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D41C8B334F00491CA5 /* AppDelegate.m */; }; + 694993D81C8B334F00491CA5 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D71C8B334F00491CA5 /* ViewController.m */; }; + 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 694993DC1C8B334F00491CA5 /* Assets.xcassets */; }; + 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */; }; + 69DCA5221C8B3D30006FF548 /* DetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5211C8B3D30006FF548 /* DetailViewController.m */; }; + 69DCA5251C8BE01F006FF548 /* DetailRootNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */; }; + 69DCA5281C8BE031006FF548 /* DetailCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5271C8BE031006FF548 /* DetailCellNode.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 694993CD1C8B334F00491CA5 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 694993D11C8B334F00491CA5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 694993D31C8B334F00491CA5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 694993D41C8B334F00491CA5 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 694993D61C8B334F00491CA5 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 694993D71C8B334F00491CA5 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 694993DC1C8B334F00491CA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 694993DF1C8B334F00491CA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 694993E11C8B334F00491CA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 69DCA5201C8B3D30006FF548 /* DetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailViewController.h; sourceTree = ""; }; + 69DCA5211C8B3D30006FF548 /* DetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailViewController.m; sourceTree = ""; }; + 69DCA5231C8BE01F006FF548 /* DetailRootNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailRootNode.h; sourceTree = ""; }; + 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailRootNode.m; sourceTree = ""; }; + 69DCA5261C8BE031006FF548 /* DetailCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailCellNode.h; sourceTree = ""; }; + 69DCA5271C8BE031006FF548 /* DetailCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailCellNode.m; sourceTree = ""; }; + 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 694993CA1C8B334F00491CA5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5CF3EF5E344946731D4F13F2 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0DFDB4376BA084DAC7C1976E /* Pods */ = { + isa = PBXGroup; + children = ( + 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */, + 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 478C8D7C412DCBDFE14640D8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 694993C41C8B334F00491CA5 = { + isa = PBXGroup; + children = ( + 694993CF1C8B334F00491CA5 /* Sample */, + 694993CE1C8B334F00491CA5 /* Products */, + 0DFDB4376BA084DAC7C1976E /* Pods */, + 478C8D7C412DCBDFE14640D8 /* Frameworks */, + ); + sourceTree = ""; + }; + 694993CE1C8B334F00491CA5 /* Products */ = { + isa = PBXGroup; + children = ( + 694993CD1C8B334F00491CA5 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 694993CF1C8B334F00491CA5 /* Sample */ = { + isa = PBXGroup; + children = ( + 694993D31C8B334F00491CA5 /* AppDelegate.h */, + 694993D41C8B334F00491CA5 /* AppDelegate.m */, + 694993D61C8B334F00491CA5 /* ViewController.h */, + 694993D71C8B334F00491CA5 /* ViewController.m */, + 69DCA5201C8B3D30006FF548 /* DetailViewController.h */, + 69DCA5211C8B3D30006FF548 /* DetailViewController.m */, + 69DCA5231C8BE01F006FF548 /* DetailRootNode.h */, + 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */, + 69DCA5261C8BE031006FF548 /* DetailCellNode.h */, + 69DCA5271C8BE031006FF548 /* DetailCellNode.m */, + 694993DC1C8B334F00491CA5 /* Assets.xcassets */, + 694993D01C8B334F00491CA5 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 694993D01C8B334F00491CA5 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 694993E11C8B334F00491CA5 /* Info.plist */, + 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */, + 694993D11C8B334F00491CA5 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 694993CC1C8B334F00491CA5 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 694993E41C8B334F00491CA5 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + 80035273449C25F4B2E1454F /* Check Pods Manifest.lock */, + 694993C91C8B334F00491CA5 /* Sources */, + 694993CA1C8B334F00491CA5 /* Frameworks */, + 694993CB1C8B334F00491CA5 /* Resources */, + 06EE2E0ABEB6289D4775A867 /* Copy Pods Resources */, + 23FC03B282CBD9014D868DF6 /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 694993CD1C8B334F00491CA5 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 694993C51C8B334F00491CA5 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = AsyncDisplayKit; + TargetAttributes = { + 694993CC1C8B334F00491CA5 = { + CreatedOnToolsVersion = 7.2.1; + }; + }; + }; + buildConfigurationList = 694993C81C8B334F00491CA5 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 694993C41C8B334F00491CA5; + productRefGroup = 694993CE1C8B334F00491CA5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 694993CC1C8B334F00491CA5 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 694993CB1C8B334F00491CA5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */, + 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 06EE2E0ABEB6289D4775A867 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 23FC03B282CBD9014D868DF6 /* 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-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 80035273449C25F4B2E1454F /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 694993C91C8B334F00491CA5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 694993D81C8B334F00491CA5 /* ViewController.m in Sources */, + 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */, + 694993D21C8B334F00491CA5 /* main.m in Sources */, + 69DCA5221C8B3D30006FF548 /* DetailViewController.m in Sources */, + 69DCA5281C8BE031006FF548 /* DetailCellNode.m in Sources */, + 69DCA5251C8BE01F006FF548 /* DetailRootNode.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 694993DF1C8B334F00491CA5 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 694993E21C8B334F00491CA5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 694993E31C8B334F00491CA5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 694993E51C8B334F00491CA5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 694993E61C8B334F00491CA5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 694993C81C8B334F00491CA5 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 694993E21C8B334F00491CA5 /* Debug */, + 694993E31C8B334F00491CA5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 694993E41C8B334F00491CA5 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 694993E51C8B334F00491CA5 /* Debug */, + 694993E61C8B334F00491CA5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 694993C51C8B334F00491CA5 /* Project object */; +} diff --git a/examples/AsyncDisplayKitOverview/Podfile b/examples/AsyncDisplayKitOverview/Podfile index e3b2757ef6..95777ae876 100644 --- a/examples/AsyncDisplayKitOverview/Podfile +++ b/examples/AsyncDisplayKitOverview/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '7.0' +platform :ios, '8.0' # Uncomment this line if you're using Swift # use_frameworks! diff --git a/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj b/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj index 5872950ccd..da40db4660 100644 --- a/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj +++ b/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj @@ -309,7 +309,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -348,7 +348,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -362,7 +362,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -375,7 +374,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m b/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m index 798c9e2a95..c49f64cca2 100644 --- a/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m +++ b/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m @@ -198,8 +198,8 @@ - (void)setupData // Set title for button node with a given font or color. If you pass in nil for font or color the default system // font and black as color will be used - [buttonNode setTitle:@"Button Title Normal" withFont:nil withColor:[UIColor blueColor] forState:ASControlStateNormal]; - [buttonNode setTitle:@"Button Title Highlighted" withFont:[UIFont systemFontOfSize:14] withColor:nil forState:ASControlStateHighlighted]; + [buttonNode setTitle:@"Button Title Normal" withFont:nil withColor:[UIColor blueColor] forState:UIControlStateNormal]; + [buttonNode setTitle:@"Button Title Highlighted" withFont:[UIFont systemFontOfSize:14] withColor:nil forState:UIControlStateHighlighted]; [buttonNode addTarget:self action:@selector(buttonPressed:) forControlEvents:ASControlNodeEventTouchUpInside]; parentNode = [self centeringParentNodeWithChild:buttonNode]; diff --git a/examples/CatDealsCollectionView/Podfile b/examples/CatDealsCollectionView/Podfile index 919de4b311..defaf55058 100644 --- a/examples/CatDealsCollectionView/Podfile +++ b/examples/CatDealsCollectionView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj b/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj index 366fe1ef9c..8f4214a411 100644 --- a/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj @@ -1,970 +1,403 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 25FDEC901BF31EE700CEB123 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ItemNode.h - sourceTree - <group> - - 25FDEC911BF31EE700CEB123 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ItemNode.m - sourceTree - <group> - - 25FDEC921BF31EE700CEB123 - - fileRef - 25FDEC911BF31EE700CEB123 - isa - PBXBuildFile - - 65F7AC562A910A742522053C - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - 7A83848C1C34359D002CDD08 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ItemViewModel.h - sourceTree - <group> - - 7A83848D1C34359D002CDD08 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ItemViewModel.m - sourceTree - <group> - - 7A83848E1C34359D002CDD08 - - fileRef - 7A83848D1C34359D002CDD08 - isa - PBXBuildFile - - 7A8384911C343680002CDD08 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - BlurbNode.h - sourceTree - <group> - - 7A8384921C343680002CDD08 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - BlurbNode.m - sourceTree - <group> - - 7A8384941C343680002CDD08 - - fileRef - 7A8384921C343680002CDD08 - isa - PBXBuildFile - - 7A8384951C344057002CDD08 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ItemStyles.h - sourceTree - <group> - - 7A8384961C344057002CDD08 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ItemStyles.m - sourceTree - <group> - - 7A8384971C344057002CDD08 - - fileRef - 7A8384961C344057002CDD08 - isa - PBXBuildFile - - 7ACD5F871C415B7500E7BE16 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - LoadingNode.h - sourceTree - <group> - - 7ACD5F881C415B7500E7BE16 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - LoadingNode.m - sourceTree - <group> - - 7ACD5F891C415B7500E7BE16 - - fileRef - 7ACD5F881C415B7500E7BE16 - isa - PBXBuildFile - - 7ACD5F941C4847C000E7BE16 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - PlaceholderNetworkImageNode.h - sourceTree - <group> - - 7ACD5F951C4847C000E7BE16 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - PlaceholderNetworkImageNode.m - sourceTree - <group> - - 7ACD5F961C4847C000E7BE16 - - fileRef - 7ACD5F951C4847C000E7BE16 - isa - PBXBuildFile - - 90A2B9C5397C46134C8A793B - - children - - F3A72D578C378357FF56486A - 65F7AC562A910A742522053C - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 91C402838901BD02685337A8 - - fileRef - F1E539014E1F516F00A8F167 - isa - PBXBuildFile - - 9BA2CEA01BB2579C00D18414 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - file.storyboard - path - Launchboard.storyboard - sourceTree - <group> - - 9BA2CEA11BB2579C00D18414 - - fileRef - 9BA2CEA01BB2579C00D18414 - isa - PBXBuildFile - - A6902C454C7661D0D277AC62 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - AC3C4A551A11F47200143C57 - - children - - AC3C4A601A11F47200143C57 - AC3C4A5F1A11F47200143C57 - 90A2B9C5397C46134C8A793B - D6E38FF0CB18E3F55CF06437 - - isa - PBXGroup - sourceTree - <group> - - AC3C4A561A11F47200143C57 - - attributes - - LastUpgradeCheck - 0610 - ORGANIZATIONNAME - Facebook - TargetAttributes - - AC3C4A5D1A11F47200143C57 - - CreatedOnToolsVersion - 6.1 - - - - buildConfigurationList - AC3C4A591A11F47200143C57 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - AC3C4A551A11F47200143C57 - productRefGroup - AC3C4A5F1A11F47200143C57 - projectDirPath - - projectReferences - - projectRoot - - targets - - AC3C4A5D1A11F47200143C57 - - - AC3C4A591A11F47200143C57 - - buildConfigurations - - AC3C4A7F1A11F47200143C57 - AC3C4A801A11F47200143C57 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - AC3C4A5A1A11F47200143C57 - - buildActionMask - 2147483647 - files - - 25FDEC921BF31EE700CEB123 - 7ACD5F891C415B7500E7BE16 - AC3C4A6A1A11F47200143C57 - 7A8384971C344057002CDD08 - FC3FCA801C2B1564009F6D6D - 7A8384941C343680002CDD08 - 7A83848E1C34359D002CDD08 - AC3C4A671A11F47200143C57 - 7ACD5F961C4847C000E7BE16 - AC3C4A641A11F47200143C57 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - AC3C4A5B1A11F47200143C57 - - buildActionMask - 2147483647 - files - - 91C402838901BD02685337A8 - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - AC3C4A5C1A11F47200143C57 - - buildActionMask - 2147483647 - files - - 9BA2CEA11BB2579C00D18414 - AC3C4A8E1A11F80C00143C57 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - AC3C4A5D1A11F47200143C57 - - buildConfigurationList - AC3C4A811A11F47200143C57 - buildPhases - - F868CFBB21824CC9521B6588 - AC3C4A5A1A11F47200143C57 - AC3C4A5B1A11F47200143C57 - AC3C4A5C1A11F47200143C57 - A6902C454C7661D0D277AC62 - B4CD33E927E6F4EE5DD6CCF0 - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - AC3C4A5E1A11F47200143C57 - productType - com.apple.product-type.application - - AC3C4A5E1A11F47200143C57 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - AC3C4A5F1A11F47200143C57 - - children - - AC3C4A5E1A11F47200143C57 - - isa - PBXGroup - name - Products - sourceTree - <group> - - AC3C4A601A11F47200143C57 - - children - - AC3C4A651A11F47200143C57 - AC3C4A661A11F47200143C57 - AC3C4A681A11F47200143C57 - AC3C4A691A11F47200143C57 - 7ACD5F941C4847C000E7BE16 - 7ACD5F951C4847C000E7BE16 - FC3FCA7E1C2B1564009F6D6D - FC3FCA7F1C2B1564009F6D6D - AC3C4A8D1A11F80C00143C57 - AC3C4A611A11F47200143C57 - 25FDEC901BF31EE700CEB123 - 25FDEC911BF31EE700CEB123 - 7A8384951C344057002CDD08 - 7A8384961C344057002CDD08 - 7A83848C1C34359D002CDD08 - 7A83848D1C34359D002CDD08 - 7A8384911C343680002CDD08 - 7A8384921C343680002CDD08 - 7ACD5F871C415B7500E7BE16 - 7ACD5F881C415B7500E7BE16 - - indentWidth - 2 - isa - PBXGroup - path - Sample - sourceTree - <group> - tabWidth - 2 - usesTabs - 0 - - AC3C4A611A11F47200143C57 - - children - - AC3C4A621A11F47200143C57 - AC3C4A631A11F47200143C57 - 9BA2CEA01BB2579C00D18414 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - AC3C4A621A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - AC3C4A631A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - AC3C4A641A11F47200143C57 - - fileRef - AC3C4A631A11F47200143C57 - isa - PBXBuildFile - - AC3C4A651A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - AC3C4A661A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - AC3C4A671A11F47200143C57 - - fileRef - AC3C4A661A11F47200143C57 - isa - PBXBuildFile - - AC3C4A681A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - AC3C4A691A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - AC3C4A6A1A11F47200143C57 - - fileRef - AC3C4A691A11F47200143C57 - isa - PBXBuildFile - - AC3C4A7F1A11F47200143C57 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_SYMBOLS_PRIVATE_EXTERN - NO - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Debug - - AC3C4A801A11F47200143C57 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - YES - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - TARGETED_DEVICE_FAMILY - 1,2 - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - AC3C4A811A11F47200143C57 - - buildConfigurations - - AC3C4A821A11F47200143C57 - AC3C4A831A11F47200143C57 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - AC3C4A821A11F47200143C57 - - baseConfigurationReference - F3A72D578C378357FF56486A - buildSettings - - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME - LaunchImage - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Debug - - AC3C4A831A11F47200143C57 - - baseConfigurationReference - 65F7AC562A910A742522053C - buildSettings - - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME - LaunchImage - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Release - - AC3C4A8D1A11F80C00143C57 - - isa - PBXFileReference - lastKnownFileType - folder.assetcatalog - path - Images.xcassets - sourceTree - <group> - - AC3C4A8E1A11F80C00143C57 - - fileRef - AC3C4A8D1A11F80C00143C57 - isa - PBXBuildFile - - B4CD33E927E6F4EE5DD6CCF0 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - D6E38FF0CB18E3F55CF06437 - - children - - F1E539014E1F516F00A8F167 - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - F1E539014E1F516F00A8F167 - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - F3A72D578C378357FF56486A - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - F868CFBB21824CC9521B6588 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - FC3FCA7E1C2B1564009F6D6D - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - PresentingViewController.h - sourceTree - <group> - - FC3FCA7F1C2B1564009F6D6D - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - PresentingViewController.m - sourceTree - <group> - - FC3FCA801C2B1564009F6D6D - - fileRef - FC3FCA7F1C2B1564009F6D6D - isa - PBXBuildFile - - - rootObject - AC3C4A561A11F47200143C57 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25FDEC911BF31EE700CEB123 /* ItemNode.m */; }; + 7A83848E1C34359D002CDD08 /* ItemViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A83848D1C34359D002CDD08 /* ItemViewModel.m */; }; + 7A8384941C343680002CDD08 /* BlurbNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A8384921C343680002CDD08 /* BlurbNode.m */; }; + 7A8384971C344057002CDD08 /* ItemStyles.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A8384961C344057002CDD08 /* ItemStyles.m */; }; + 7ACD5F891C415B7500E7BE16 /* LoadingNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD5F881C415B7500E7BE16 /* LoadingNode.m */; }; + 7ACD5F961C4847C000E7BE16 /* PlaceholderNetworkImageNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD5F951C4847C000E7BE16 /* PlaceholderNetworkImageNode.m */; }; + 91C402838901BD02685337A8 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F1E539014E1F516F00A8F167 /* libPods-Sample.a */; }; + 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; }; + AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; }; + AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; }; + AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; }; + AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; }; + FC3FCA801C2B1564009F6D6D /* PresentingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 25FDEC901BF31EE700CEB123 /* ItemNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ItemNode.h; sourceTree = ""; }; + 25FDEC911BF31EE700CEB123 /* ItemNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItemNode.m; sourceTree = ""; }; + 65F7AC562A910A742522053C /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + 7A83848C1C34359D002CDD08 /* ItemViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ItemViewModel.h; sourceTree = ""; }; + 7A83848D1C34359D002CDD08 /* ItemViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItemViewModel.m; sourceTree = ""; }; + 7A8384911C343680002CDD08 /* BlurbNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlurbNode.h; sourceTree = ""; }; + 7A8384921C343680002CDD08 /* BlurbNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlurbNode.m; sourceTree = ""; }; + 7A8384951C344057002CDD08 /* ItemStyles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ItemStyles.h; sourceTree = ""; }; + 7A8384961C344057002CDD08 /* ItemStyles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItemStyles.m; sourceTree = ""; }; + 7ACD5F871C415B7500E7BE16 /* LoadingNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoadingNode.h; sourceTree = ""; }; + 7ACD5F881C415B7500E7BE16 /* LoadingNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LoadingNode.m; sourceTree = ""; }; + 7ACD5F941C4847C000E7BE16 /* PlaceholderNetworkImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaceholderNetworkImageNode.h; sourceTree = ""; }; + 7ACD5F951C4847C000E7BE16 /* PlaceholderNetworkImageNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaceholderNetworkImageNode.m; sourceTree = ""; }; + 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Launchboard.storyboard; sourceTree = ""; }; + AC3C4A5E1A11F47200143C57 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + AC3C4A621A11F47200143C57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AC3C4A631A11F47200143C57 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + AC3C4A651A11F47200143C57 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + AC3C4A661A11F47200143C57 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + AC3C4A681A11F47200143C57 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + AC3C4A691A11F47200143C57 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + F1E539014E1F516F00A8F167 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + F3A72D578C378357FF56486A /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PresentingViewController.h; sourceTree = ""; }; + FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PresentingViewController.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + AC3C4A5B1A11F47200143C57 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 91C402838901BD02685337A8 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 90A2B9C5397C46134C8A793B /* Pods */ = { + isa = PBXGroup; + children = ( + F3A72D578C378357FF56486A /* Pods-Sample.debug.xcconfig */, + 65F7AC562A910A742522053C /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + AC3C4A551A11F47200143C57 = { + isa = PBXGroup; + children = ( + AC3C4A601A11F47200143C57 /* Sample */, + AC3C4A5F1A11F47200143C57 /* Products */, + 90A2B9C5397C46134C8A793B /* Pods */, + D6E38FF0CB18E3F55CF06437 /* Frameworks */, + ); + sourceTree = ""; + }; + AC3C4A5F1A11F47200143C57 /* Products */ = { + isa = PBXGroup; + children = ( + AC3C4A5E1A11F47200143C57 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + AC3C4A601A11F47200143C57 /* Sample */ = { + isa = PBXGroup; + children = ( + AC3C4A651A11F47200143C57 /* AppDelegate.h */, + AC3C4A661A11F47200143C57 /* AppDelegate.m */, + AC3C4A681A11F47200143C57 /* ViewController.h */, + AC3C4A691A11F47200143C57 /* ViewController.m */, + 7ACD5F941C4847C000E7BE16 /* PlaceholderNetworkImageNode.h */, + 7ACD5F951C4847C000E7BE16 /* PlaceholderNetworkImageNode.m */, + FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */, + FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */, + AC3C4A8D1A11F80C00143C57 /* Images.xcassets */, + AC3C4A611A11F47200143C57 /* Supporting Files */, + 25FDEC901BF31EE700CEB123 /* ItemNode.h */, + 25FDEC911BF31EE700CEB123 /* ItemNode.m */, + 7A8384951C344057002CDD08 /* ItemStyles.h */, + 7A8384961C344057002CDD08 /* ItemStyles.m */, + 7A83848C1C34359D002CDD08 /* ItemViewModel.h */, + 7A83848D1C34359D002CDD08 /* ItemViewModel.m */, + 7A8384911C343680002CDD08 /* BlurbNode.h */, + 7A8384921C343680002CDD08 /* BlurbNode.m */, + 7ACD5F871C415B7500E7BE16 /* LoadingNode.h */, + 7ACD5F881C415B7500E7BE16 /* LoadingNode.m */, + ); + indentWidth = 2; + path = Sample; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + AC3C4A611A11F47200143C57 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + AC3C4A621A11F47200143C57 /* Info.plist */, + AC3C4A631A11F47200143C57 /* main.m */, + 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D6E38FF0CB18E3F55CF06437 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F1E539014E1F516F00A8F167 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + AC3C4A5D1A11F47200143C57 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */, + AC3C4A5A1A11F47200143C57 /* Sources */, + AC3C4A5B1A11F47200143C57 /* Frameworks */, + AC3C4A5C1A11F47200143C57 /* Resources */, + A6902C454C7661D0D277AC62 /* Copy Pods Resources */, + B4CD33E927E6F4EE5DD6CCF0 /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = AC3C4A5E1A11F47200143C57 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + AC3C4A561A11F47200143C57 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + AC3C4A5D1A11F47200143C57 = { + CreatedOnToolsVersion = 6.1; + }; + }; + }; + buildConfigurationList = AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = AC3C4A551A11F47200143C57; + productRefGroup = AC3C4A5F1A11F47200143C57 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + AC3C4A5D1A11F47200143C57 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + AC3C4A5C1A11F47200143C57 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */, + AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + A6902C454C7661D0D277AC62 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + B4CD33E927E6F4EE5DD6CCF0 /* 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-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + AC3C4A5A1A11F47200143C57 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */, + 7ACD5F891C415B7500E7BE16 /* LoadingNode.m in Sources */, + AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */, + 7A8384971C344057002CDD08 /* ItemStyles.m in Sources */, + FC3FCA801C2B1564009F6D6D /* PresentingViewController.m in Sources */, + 7A8384941C343680002CDD08 /* BlurbNode.m in Sources */, + 7A83848E1C34359D002CDD08 /* ItemViewModel.m in Sources */, + AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */, + 7ACD5F961C4847C000E7BE16 /* PlaceholderNetworkImageNode.m in Sources */, + AC3C4A641A11F47200143C57 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + AC3C4A7F1A11F47200143C57 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AC3C4A801A11F47200143C57 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + AC3C4A821A11F47200143C57 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F3A72D578C378357FF56486A /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AC3C4A831A11F47200143C57 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 65F7AC562A910A742522053C /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AC3C4A7F1A11F47200143C57 /* Debug */, + AC3C4A801A11F47200143C57 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AC3C4A821A11F47200143C57 /* Debug */, + AC3C4A831A11F47200143C57 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = AC3C4A561A11F47200143C57 /* Project object */; +} diff --git a/examples/CatDealsCollectionView/Sample/BlurbNode.h b/examples/CatDealsCollectionView/Sample/BlurbNode.h index 762352e76f..e6574bcd05 100644 --- a/examples/CatDealsCollectionView/Sample/BlurbNode.h +++ b/examples/CatDealsCollectionView/Sample/BlurbNode.h @@ -22,6 +22,4 @@ */ @interface BlurbNode : ASCellNode -+ (CGFloat)desiredHeightForWidth:(CGFloat)width; - @end diff --git a/examples/CatDealsCollectionView/Sample/BlurbNode.m b/examples/CatDealsCollectionView/Sample/BlurbNode.m index 262b860184..7dbe86a8b8 100644 --- a/examples/CatDealsCollectionView/Sample/BlurbNode.m +++ b/examples/CatDealsCollectionView/Sample/BlurbNode.m @@ -23,7 +23,6 @@ #import #import -static CGFloat kFixedHeight = 75.0f; static CGFloat kTextPadding = 10.0f; @interface BlurbNode () @@ -39,10 +38,6 @@ @implementation BlurbNode #pragma mark - #pragma mark ASCellNode. -+ (CGFloat)desiredHeightForWidth:(CGFloat)width { - return kFixedHeight; -} - - (instancetype)init { if (!(self = [super init])) diff --git a/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Contents.json b/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Contents.json index f0fce54771..c5fda8e395 100644 --- a/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Contents.json +++ b/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -1,36 +1,6 @@ { "images" : [ - { - "orientation" : "portrait", - "idiom" : "iphone", - "filename" : "Default-568h@2x.png", - "minimum-system-version" : "7.0", - "subtype" : "retina4", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "scale" : "1x", - "orientation" : "portrait" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "orientation" : "portrait" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "filename" : "Default-568h@2x.png", - "subtype" : "retina4", - "scale" : "2x" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "minimum-system-version" : "7.0", - "scale" : "2x" - } + ], "info" : { "version" : 1, diff --git a/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png deleted file mode 100644 index 1547a98454..0000000000 Binary files a/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png and /dev/null differ diff --git a/examples/CatDealsCollectionView/Sample/ItemNode.m b/examples/CatDealsCollectionView/Sample/ItemNode.m index 4ee001431d..79d8c2ff21 100644 --- a/examples/CatDealsCollectionView/Sample/ItemNode.m +++ b/examples/CatDealsCollectionView/Sample/ItemNode.m @@ -223,11 +223,11 @@ - (void)setHighlighted:(BOOL)highlighted - (void)displayWillStart { [super displayWillStart]; - [self fetchData]; + [self didEnterPreloadState]; } -- (void)fetchData { - [super fetchData]; +- (void)didEnterPreloadState { + [super didEnterPreloadState]; if (self.viewModel) { [self loadImage]; } diff --git a/examples/CatDealsCollectionView/Sample/LoadingNode.h b/examples/CatDealsCollectionView/Sample/LoadingNode.h index 109cf61a32..d144de01a9 100644 --- a/examples/CatDealsCollectionView/Sample/LoadingNode.h +++ b/examples/CatDealsCollectionView/Sample/LoadingNode.h @@ -21,6 +21,4 @@ @interface LoadingNode : ASCellNode -+ (CGFloat)desiredHeightForWidth:(CGFloat)width; - @end diff --git a/examples/CatDealsCollectionView/Sample/LoadingNode.m b/examples/CatDealsCollectionView/Sample/LoadingNode.m index b9b4f9788f..4fa29d6e3d 100644 --- a/examples/CatDealsCollectionView/Sample/LoadingNode.m +++ b/examples/CatDealsCollectionView/Sample/LoadingNode.m @@ -24,8 +24,6 @@ #import #import -static CGFloat kFixedHeight = 200.0f; - @interface LoadingNode () { ASDisplayNode *_loadingSpinner; @@ -39,10 +37,6 @@ @implementation LoadingNode #pragma mark - #pragma mark ASCellNode. -+ (CGFloat)desiredHeightForWidth:(CGFloat)width { - return kFixedHeight; -} - - (instancetype)init { if (!(self = [super init])) @@ -61,10 +55,6 @@ - (instancetype)init return self; } -- (void)layout { - [super layout]; -} - - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { ASCenterLayoutSpec *centerSpec = [[ASCenterLayoutSpec alloc] init]; diff --git a/examples/CatDealsCollectionView/Sample/ViewController.m b/examples/CatDealsCollectionView/Sample/ViewController.m index 8726968ef4..6d693f690c 100644 --- a/examples/CatDealsCollectionView/Sample/ViewController.m +++ b/examples/CatDealsCollectionView/Sample/ViewController.m @@ -27,9 +27,8 @@ static const NSInteger kBatchSize = 20; static const CGFloat kHorizontalSectionPadding = 10.0f; -static const CGFloat kVerticalSectionPadding = 20.0f; -@interface ViewController () +@interface ViewController () { ASCollectionNode *_collectionNode; NSMutableArray *_data; @@ -203,27 +202,24 @@ - (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWit }]; } -#pragma mark - ASCollectionViewDelegateFlowLayout +#pragma mark - ASCollectionDelegateFlowLayout -- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { - return UIEdgeInsetsMake(kVerticalSectionPadding, kHorizontalSectionPadding, kVerticalSectionPadding, kHorizontalSectionPadding); -} - -- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { +- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForHeaderInSection:(NSInteger)section +{ if (section == 0) { - CGFloat width = CGRectGetWidth(self.view.frame) - 2 * kHorizontalSectionPadding; - return CGSizeMake(width, [BlurbNode desiredHeightForWidth:width]); + return ASSizeRangeUnconstrained; + } else { + return ASSizeRangeZero; } - return CGSizeZero; } -- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { +- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section +{ if (section == 0) { - CGFloat width = CGRectGetWidth(self.view.frame); - return CGSizeMake(width, [LoadingNode desiredHeightForWidth:width]); + return ASSizeRangeUnconstrained; + } else { + return ASSizeRangeZero; } - return CGSizeZero; } - @end diff --git a/examples/CustomCollectionView-Swift/Podfile b/examples/CustomCollectionView-Swift/Podfile new file mode 100644 index 0000000000..77782621e2 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Podfile @@ -0,0 +1,8 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' + +use_frameworks! + +target 'Sample' do + pod 'AsyncDisplayKit', :path => '../..' +end \ No newline at end of file diff --git a/examples/LayoutSpecPlayground/Sample.xcodeproj/project.pbxproj b/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.pbxproj similarity index 58% rename from examples/LayoutSpecPlayground/Sample.xcodeproj/project.pbxproj rename to examples/CustomCollectionView-Swift/Sample.xcodeproj/project.pbxproj index 4f0390c177..621297c9c2 100644 --- a/examples/LayoutSpecPlayground/Sample.xcodeproj/project.pbxproj +++ b/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.pbxproj @@ -10,65 +10,66 @@ 46 objects - 0585427F19D4DBE100606EA6 + 27F2D2683285DCB73EE734BB + fileRef + F7DA4A9952245B7E9BA8201F isa - PBXFileReference - lastKnownFileType - image.png - name - Default-568h@2x.png - path - ../Default-568h@2x.png - sourceTree - <group> + PBXBuildFile - 0585428019D4DBE100606EA6 + 3A21D910663270E99063573E - fileRef - 0585427F19D4DBE100606EA6 + children + + A9E9A143FF858FD89A482A84 + 73F1C0E45062A0A8CDC033A1 + isa - PBXBuildFile + PBXGroup + name + Pods + sourceTree + <group> - 05E2127819D4DB510098F589 + 5D823AC81DD3B7760075E14A children - 05E2128319D4DB510098F589 - 05E2128219D4DB510098F589 - 1A943BF0259746F18D6E423F - 1AE410B73DA5C3BD087ACDD7 + 5D823AD31DD3B7770075E14A + 5D823AD21DD3B7770075E14A + 3A21D910663270E99063573E + 728C7877715727493EFEE42D - indentWidth - 2 isa PBXGroup sourceTree <group> - tabWidth - 2 - usesTabs - 0 - 05E2127919D4DB510098F589 + 5D823AC91DD3B7760075E14A attributes + LastSwiftUpdateCheck + 0810 LastUpgradeCheck - 0720 + 0810 ORGANIZATIONNAME - Facebook + AsyncDisplayKit TargetAttributes - 05E2128019D4DB510098F589 + 5D823AD01DD3B7770075E14A CreatedOnToolsVersion - 6.0.1 + 8.1 + DevelopmentTeam + 888KTQ92ZP + ProvisioningStyle + Automatic buildConfigurationList - 05E2127C19D4DB510098F589 + 5D823ACC1DD3B7760075E14A compatibilityVersion Xcode 3.2 developmentRegion @@ -83,9 +84,9 @@ Base mainGroup - 05E2127819D4DB510098F589 + 5D823AC81DD3B7760075E14A productRefGroup - 05E2128219D4DB510098F589 + 5D823AD21DD3B7770075E14A projectDirPath projectReferences @@ -94,15 +95,15 @@ targets - 05E2128019D4DB510098F589 + 5D823AD01DD3B7770075E14A - 05E2127C19D4DB510098F589 + 5D823ACC1DD3B7760075E14A buildConfigurations - 05E212A219D4DB510098F589 - 05E212A319D4DB510098F589 + 5D823AE11DD3B7770075E14A + 5D823AE21DD3B7770075E14A defaultConfigurationIsVisible 0 @@ -111,66 +112,62 @@ isa XCConfigurationList - 05E2127D19D4DB510098F589 + 5D823ACD1DD3B7770075E14A buildActionMask 2147483647 files - 76466F321C9DFFC4006C4D2D - 05E2128719D4DB510098F589 - 7602C7651CA4F83100D0D917 - 76466F341C9DFFC4006C4D2D - 76F58D5C1C9E15C1004512CC - 76466F351C9DFFC4006C4D2D - 80A6A5181D88F08F00473431 + 5D823AE71DD3B7D30075E14A + 5D823AD71DD3B7770075E14A + 5D823AD51DD3B7770075E14A + 5D823AE91DD3B7D70075E14A isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 - 05E2127E19D4DB510098F589 + 5D823ACE1DD3B7770075E14A buildActionMask 2147483647 files - B971D066CC023A00C53D8575 + 27F2D2683285DCB73EE734BB isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 - 05E2127F19D4DB510098F589 + 5D823ACF1DD3B7770075E14A buildActionMask 2147483647 files - 0585428019D4DBE100606EA6 - 6C2C82AC19EE274300767484 - 6C2C82AD19EE274300767484 - 7602C7671CA4FB5300D0D917 + 5D823ADF1DD3B7770075E14A + 5D823ADC1DD3B7770075E14A + 5D823ADA1DD3B7770075E14A isa PBXResourcesBuildPhase runOnlyForDeploymentPostprocessing 0 - 05E2128019D4DB510098F589 + 5D823AD01DD3B7770075E14A buildConfigurationList - 05E212A419D4DB510098F589 + 5D823AE31DD3B7770075E14A buildPhases - E080B80F89C34A25B3488E26 - 05E2127D19D4DB510098F589 - 05E2127E19D4DB510098F589 - 05E2127F19D4DB510098F589 - F012A6F39E0149F18F564F50 - 2291CF7AF2D4B273DDAD8AAC + BAA73690D42731AA5D8001CF + 5D823ACD1DD3B7770075E14A + 5D823ACE1DD3B7770075E14A + 5D823ACF1DD3B7770075E14A + 8293091514A70C5E7E487A36 + 641DF857294FFEAA1878D05C buildRules @@ -183,11 +180,11 @@ productName Sample productReference - 05E2128119D4DB510098F589 + 5D823AD11DD3B7770075E14A productType com.apple.product-type.application - 05E2128119D4DB510098F589 + 5D823AD11DD3B7770075E14A explicitFileType wrapper.application @@ -200,11 +197,11 @@ sourceTree BUILT_PRODUCTS_DIR - 05E2128219D4DB510098F589 + 5D823AD21DD3B7770075E14A children - 05E2128119D4DB510098F589 + 5D823AD11DD3B7770075E14A isa PBXGroup @@ -213,23 +210,18 @@ sourceTree <group> - 05E2128319D4DB510098F589 + 5D823AD31DD3B7770075E14A children - 76466F2A1C9DFFC4006C4D2D - 76466F2B1C9DFFC4006C4D2D - 76466F301C9DFFC4006C4D2D - 76466F311C9DFFC4006C4D2D - 76F58D5A1C9E15C1004512CC - 76F58D5B1C9E15C1004512CC - 76466F2E1C9DFFC4006C4D2D - 76466F2F1C9DFFC4006C4D2D - 80A6A5161D88F08F00473431 - 80A6A5171D88F08F00473431 - 7602C7631CA4F83100D0D917 - 7602C7641CA4F83100D0D917 - 05E2128419D4DB510098F589 + 5D823AD41DD3B7770075E14A + 5D823AD61DD3B7770075E14A + 5D823AE81DD3B7D70075E14A + 5D823AE61DD3B7D30075E14A + 5D823AD81DD3B7770075E14A + 5D823ADB1DD3B7770075E14A + 5D823ADD1DD3B7770075E14A + 5D823AE01DD3B7770075E14A isa PBXGroup @@ -238,59 +230,145 @@ sourceTree <group> - 05E2128419D4DB510098F589 + 5D823AD41DD3B7770075E14A + + isa + PBXFileReference + lastKnownFileType + sourcecode.swift + path + AppDelegate.swift + sourceTree + <group> + + 5D823AD51DD3B7770075E14A + + fileRef + 5D823AD41DD3B7770075E14A + isa + PBXBuildFile + + 5D823AD61DD3B7770075E14A + + isa + PBXFileReference + lastKnownFileType + sourcecode.swift + path + ViewController.swift + sourceTree + <group> + + 5D823AD71DD3B7770075E14A + + fileRef + 5D823AD61DD3B7770075E14A + isa + PBXBuildFile + + 5D823AD81DD3B7770075E14A children - 0585427F19D4DBE100606EA6 - 6C2C82AA19EE274300767484 - 7602C7661CA4FB5300D0D917 - 6C2C82AB19EE274300767484 - 05E2128519D4DB510098F589 - 05E2128619D4DB510098F589 + 5D823AD91DD3B7770075E14A isa - PBXGroup + PBXVariantGroup name - Supporting Files + Main.storyboard sourceTree <group> - 05E2128519D4DB510098F589 + 5D823AD91DD3B7770075E14A isa PBXFileReference lastKnownFileType - text.plist.xml + file.storyboard + name + Base path - Info.plist + Base.lproj/Main.storyboard + sourceTree + <group> + + 5D823ADA1DD3B7770075E14A + + fileRef + 5D823AD81DD3B7770075E14A + isa + PBXBuildFile + + 5D823ADB1DD3B7770075E14A + + isa + PBXFileReference + lastKnownFileType + folder.assetcatalog + path + Assets.xcassets + sourceTree + <group> + + 5D823ADC1DD3B7770075E14A + + fileRef + 5D823ADB1DD3B7770075E14A + isa + PBXBuildFile + + 5D823ADD1DD3B7770075E14A + + children + + 5D823ADE1DD3B7770075E14A + + isa + PBXVariantGroup + name + LaunchScreen.storyboard sourceTree <group> - 05E2128619D4DB510098F589 + 5D823ADE1DD3B7770075E14A isa PBXFileReference lastKnownFileType - sourcecode.c.objc + file.storyboard + name + Base path - main.m + Base.lproj/LaunchScreen.storyboard sourceTree <group> - 05E2128719D4DB510098F589 + 5D823ADF1DD3B7770075E14A fileRef - 05E2128619D4DB510098F589 + 5D823ADD1DD3B7770075E14A isa PBXBuildFile - 05E212A219D4DB510098F589 + 5D823AE01DD3B7770075E14A + + isa + PBXFileReference + lastKnownFileType + text.plist.xml + path + Info.plist + sourceTree + <group> + + 5D823AE11DD3B7770075E14A buildSettings ALWAYS_SEARCH_USER_PATHS NO + CLANG_ANALYZER_NONNULL + YES CLANG_CXX_LANGUAGE_STANDARD gnu++0x CLANG_CXX_LIBRARY @@ -305,14 +383,22 @@ YES CLANG_WARN_DIRECT_OBJC_ISA_USAGE YES_ERROR + CLANG_WARN_DOCUMENTATION_COMMENTS + YES CLANG_WARN_EMPTY_BODY YES CLANG_WARN_ENUM_CONVERSION YES + CLANG_WARN_INFINITE_RECURSION + YES CLANG_WARN_INT_CONVERSION YES CLANG_WARN_OBJC_ROOT_CLASS YES_ERROR + CLANG_WARN_SUSPICIOUS_MOVE + YES + CLANG_WARN_SUSPICIOUS_MOVES + YES CLANG_WARN_UNREACHABLE_CODE YES CLANG_WARN__DUPLICATE_METHOD_MATCH @@ -321,6 +407,8 @@ iPhone Developer COPY_PHASE_STRIP NO + DEBUG_INFORMATION_FORMAT + dwarf ENABLE_STRICT_OBJC_MSGSEND YES ENABLE_TESTABILITY @@ -329,6 +417,8 @@ gnu99 GCC_DYNAMIC_NO_PIC NO + GCC_NO_COMMON_BLOCKS + YES GCC_OPTIMIZATION_LEVEL 0 GCC_PREPROCESSOR_DEFINITIONS @@ -336,8 +426,6 @@ DEBUG=1 $(inherited) - GCC_SYMBOLS_PRIVATE_EXTERN - NO GCC_WARN_64_TO_32_BIT_CONVERSION YES GCC_WARN_ABOUT_RETURN_TYPE @@ -351,25 +439,31 @@ GCC_WARN_UNUSED_VARIABLE YES IPHONEOS_DEPLOYMENT_TARGET - 7.1 + 9.3 MTL_ENABLE_DEBUG_INFO YES ONLY_ACTIVE_ARCH YES SDKROOT iphoneos + SWIFT_ACTIVE_COMPILATION_CONDITIONS + DEBUG + SWIFT_OPTIMIZATION_LEVEL + -Onone isa XCBuildConfiguration name Debug - 05E212A319D4DB510098F589 + 5D823AE21DD3B7770075E14A buildSettings ALWAYS_SEARCH_USER_PATHS NO + CLANG_ANALYZER_NONNULL + YES CLANG_CXX_LANGUAGE_STANDARD gnu++0x CLANG_CXX_LIBRARY @@ -384,14 +478,22 @@ YES CLANG_WARN_DIRECT_OBJC_ISA_USAGE YES_ERROR + CLANG_WARN_DOCUMENTATION_COMMENTS + YES CLANG_WARN_EMPTY_BODY YES CLANG_WARN_ENUM_CONVERSION YES + CLANG_WARN_INFINITE_RECURSION + YES CLANG_WARN_INT_CONVERSION YES CLANG_WARN_OBJC_ROOT_CLASS YES_ERROR + CLANG_WARN_SUSPICIOUS_MOVE + YES + CLANG_WARN_SUSPICIOUS_MOVES + YES CLANG_WARN_UNREACHABLE_CODE YES CLANG_WARN__DUPLICATE_METHOD_MATCH @@ -399,13 +501,17 @@ CODE_SIGN_IDENTITY[sdk=iphoneos*] iPhone Developer COPY_PHASE_STRIP - YES + NO + DEBUG_INFORMATION_FORMAT + dwarf-with-dsym ENABLE_NS_ASSERTIONS NO ENABLE_STRICT_OBJC_MSGSEND YES GCC_C_LANGUAGE_STANDARD gnu99 + GCC_NO_COMMON_BLOCKS + YES GCC_WARN_64_TO_32_BIT_CONVERSION YES GCC_WARN_ABOUT_RETURN_TYPE @@ -419,11 +525,13 @@ GCC_WARN_UNUSED_VARIABLE YES IPHONEOS_DEPLOYMENT_TARGET - 7.1 + 9.3 MTL_ENABLE_DEBUG_INFO NO SDKROOT iphoneos + SWIFT_OPTIMIZATION_LEVEL + -Owholemodule VALIDATE_PRODUCT YES @@ -432,12 +540,12 @@ name Release - 05E212A419D4DB510098F589 + 5D823AE31DD3B7770075E14A buildConfigurations - 05E212A519D4DB510098F589 - 05E212A619D4DB510098F589 + 5D823AE41DD3B7770075E14A + 5D823AE51DD3B7770075E14A defaultConfigurationIsVisible 0 @@ -446,104 +554,99 @@ isa XCConfigurationList - 05E212A519D4DB510098F589 + 5D823AE41DD3B7770075E14A baseConfigurationReference - FDF496F367580DF9280D36EA + A9E9A143FF858FD89A482A84 buildSettings ASSETCATALOG_COMPILER_APPICON_NAME AppIcon + DEVELOPMENT_TEAM + 888KTQ92ZP INFOPLIST_FILE Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 LD_RUNPATH_SEARCH_PATHS $(inherited) @executable_path/Frameworks PRODUCT_BUNDLE_IDENTIFIER - com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + com.facebook.AsyncDisplayKit.Sample PRODUCT_NAME $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 + SWIFT_VERSION + 3.0 isa XCBuildConfiguration name Debug - 05E212A619D4DB510098F589 + 5D823AE51DD3B7770075E14A baseConfigurationReference - 5C5154389F056C672F4E9EEA + 73F1C0E45062A0A8CDC033A1 buildSettings ASSETCATALOG_COMPILER_APPICON_NAME AppIcon + DEVELOPMENT_TEAM + 888KTQ92ZP INFOPLIST_FILE Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 LD_RUNPATH_SEARCH_PATHS $(inherited) @executable_path/Frameworks PRODUCT_BUNDLE_IDENTIFIER - com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + com.facebook.AsyncDisplayKit.Sample PRODUCT_NAME $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 + SWIFT_VERSION + 3.0 isa XCBuildConfiguration name Release - 088AA6578212BE9BFBB07B70 + 5D823AE61DD3B7D30075E14A - includeInIndex - 1 + fileEncoding + 4 isa PBXFileReference lastKnownFileType - text.xcconfig - name - Pods.release.xcconfig + sourcecode.swift path - Pods/Target Support Files/Pods/Pods.release.xcconfig + MosaicCollectionViewLayout.swift sourceTree <group> - 1A943BF0259746F18D6E423F + 5D823AE71DD3B7D30075E14A - children - - 3D24B17D1E4A4E7A9566C5E9 - 54A6EB3DE8D9A9EC4AE2867D - + fileRef + 5D823AE61DD3B7D30075E14A isa - PBXGroup - name - Frameworks - sourceTree - <group> + PBXBuildFile - 1AE410B73DA5C3BD087ACDD7 + 5D823AE81DD3B7D70075E14A - children - - C068F1D3F0CC317E895FCDAB - 088AA6578212BE9BFBB07B70 - FDF496F367580DF9280D36EA - 5C5154389F056C672F4E9EEA - + fileEncoding + 4 isa - PBXGroup - name - Pods + PBXFileReference + lastKnownFileType + sourcecode.swift + path + ImageCellNode.swift sourceTree <group> - 2291CF7AF2D4B273DDAD8AAC + 5D823AE91DD3B7D70075E14A + + fileRef + 5D823AE81DD3B7D70075E14A + isa + PBXBuildFile + + 641DF857294FFEAA1878D05C buildActionMask 2147483647 @@ -554,7 +657,7 @@ isa PBXShellScriptBuildPhase name - [CP] Embed Pods Frameworks + [CP] Copy Pods Resources outputPaths runOnlyForDeploymentPostprocessing @@ -562,38 +665,25 @@ shellPath /bin/sh shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" + "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" showEnvVarsInLog 0 - 3D24B17D1E4A4E7A9566C5E9 + 728C7877715727493EFEE42D - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods.a - sourceTree - BUILT_PRODUCTS_DIR - - 54A6EB3DE8D9A9EC4AE2867D - - explicitFileType - archive.ar - includeInIndex - 0 + children + + F7DA4A9952245B7E9BA8201F + isa - PBXFileReference - path - libPods-Sample.a + PBXGroup + name + Frameworks sourceTree - BUILT_PRODUCTS_DIR + <group> - 5C5154389F056C672F4E9EEA + 73F1C0E45062A0A8CDC033A1 includeInIndex 1 @@ -608,266 +698,31 @@ sourceTree <group> - 6C2C82AA19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-667h@2x.png - sourceTree - SOURCE_ROOT - - 6C2C82AB19EE274300767484 + 8293091514A70C5E7E487A36 + buildActionMask + 2147483647 + files + + inputPaths + isa - PBXFileReference - lastKnownFileType - image.png - path - Default-736h@3x.png - sourceTree - SOURCE_ROOT - - 6C2C82AC19EE274300767484 - - fileRef - 6C2C82AA19EE274300767484 - isa - PBXBuildFile - - 6C2C82AD19EE274300767484 - - fileRef - 6C2C82AB19EE274300767484 - isa - PBXBuildFile - - 7602C7631CA4F83100D0D917 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - Utilities.h - sourceTree - <group> - - 7602C7641CA4F83100D0D917 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - Utilities.m - sourceTree - <group> - - 7602C7651CA4F83100D0D917 - - fileRef - 7602C7641CA4F83100D0D917 - isa - PBXBuildFile - - 7602C7661CA4FB5300D0D917 - - isa - PBXFileReference - lastKnownFileType - image.png - path - resizeHandle.png - sourceTree - <group> - - 7602C7671CA4FB5300D0D917 - - fileRef - 7602C7661CA4FB5300D0D917 - isa - PBXBuildFile - - 76466F2A1C9DFFC4006C4D2D - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - 76466F2B1C9DFFC4006C4D2D - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - 76466F2E1C9DFFC4006C4D2D - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - PhotoPostNode.h - sourceTree - <group> - - 76466F2F1C9DFFC4006C4D2D - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - PhotoPostNode.m - sourceTree - <group> - - 76466F301C9DFFC4006C4D2D - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - 76466F311C9DFFC4006C4D2D - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - 76466F321C9DFFC4006C4D2D - - fileRef - 76466F2B1C9DFFC4006C4D2D - isa - PBXBuildFile - - 76466F341C9DFFC4006C4D2D - - fileRef - 76466F2F1C9DFFC4006C4D2D - isa - PBXBuildFile - - 76466F351C9DFFC4006C4D2D - - fileRef - 76466F311C9DFFC4006C4D2D - isa - PBXBuildFile - - 76F58D5A1C9E15C1004512CC - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - PlaygroundContainerNode.h - sourceTree - <group> - - 76F58D5B1C9E15C1004512CC - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - PlaygroundContainerNode.m - sourceTree - <group> - - 76F58D5C1C9E15C1004512CC - - fileRef - 76F58D5B1C9E15C1004512CC - isa - PBXBuildFile - - 80A6A5161D88F08F00473431 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - LayoutExampleNodes.h - sourceTree - <group> - - 80A6A5171D88F08F00473431 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - LayoutExampleNodes.m - sourceTree - <group> - - 80A6A5181D88F08F00473431 - - fileRef - 80A6A5171D88F08F00473431 - isa - PBXBuildFile - - B971D066CC023A00C53D8575 - - fileRef - 54A6EB3DE8D9A9EC4AE2867D - isa - PBXBuildFile + PBXShellScriptBuildPhase + name + [CP] Embed Pods Frameworks + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" + + showEnvVarsInLog + 0 - C068F1D3F0CC317E895FCDAB + A9E9A143FF858FD89A482A84 includeInIndex 1 @@ -876,13 +731,13 @@ lastKnownFileType text.xcconfig name - Pods.debug.xcconfig + Pods-Sample.debug.xcconfig path - Pods/Target Support Files/Pods/Pods.debug.xcconfig + Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig sourceTree <group> - E080B80F89C34A25B3488E26 + BAA73690D42731AA5D8001CF buildActionMask 2147483647 @@ -912,47 +767,21 @@ fi showEnvVarsInLog 0 - F012A6F39E0149F18F564F50 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - [CP] Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - FDF496F367580DF9280D36EA + F7DA4A9952245B7E9BA8201F + explicitFileType + wrapper.framework includeInIndex - 1 + 0 isa PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig + Pods_Sample.framework sourceTree - <group> + BUILT_PRODUCTS_DIR rootObject - 05E2127919D4DB510098F589 + 5D823AC91DD3B7760075E14A diff --git a/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/LayoutSpecPlayground/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/CustomCollectionView-Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme similarity index 91% rename from examples/LayoutSpecPlayground/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme rename to examples/CustomCollectionView-Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme index d41d58c5d8..1c12aaa4d4 100644 --- a/examples/LayoutSpecPlayground/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme +++ b/examples/CustomCollectionView-Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -1,6 +1,6 @@ @@ -32,7 +32,7 @@ @@ -55,7 +55,7 @@ runnableDebuggingMode = "0"> @@ -74,7 +74,7 @@ runnableDebuggingMode = "0"> diff --git a/examples/CustomCollectionView-Swift/Sample/AppDelegate.swift b/examples/CustomCollectionView-Swift/Sample/AppDelegate.swift new file mode 100644 index 0000000000..13e0670d1b --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/AppDelegate.swift @@ -0,0 +1,57 @@ +// +// AppDelegate.swift +// Sample +// +// Created by Rajeev Gupta on 11/9/16. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..b8236c6534 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,48 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_0.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_0.imageset/Contents.json new file mode 100644 index 0000000000..09ec0851ee --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_0.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_0.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_0.imageset/image_0.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_0.imageset/image_0.jpg new file mode 100644 index 0000000000..4a365897ea Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_0.imageset/image_0.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_1.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_1.imageset/Contents.json new file mode 100644 index 0000000000..6d2e9f5f7c --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_1.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_1.imageset/image_1.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_1.imageset/image_1.jpg new file mode 100644 index 0000000000..5cb4828f44 Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_1.imageset/image_1.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_10.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_10.imageset/Contents.json new file mode 100644 index 0000000000..ea10700189 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_10.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_10.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_10.imageset/image_10.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_10.imageset/image_10.jpg new file mode 100644 index 0000000000..ea5cd6d268 Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_10.imageset/image_10.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_11.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_11.imageset/Contents.json new file mode 100644 index 0000000000..dc85469057 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_11.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_11.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_11.imageset/image_11.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_11.imageset/image_11.jpg new file mode 100644 index 0000000000..e93c68e512 Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_11.imageset/image_11.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_12.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_12.imageset/Contents.json new file mode 100644 index 0000000000..a6d99003d1 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_12.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_12.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_12.imageset/image_12.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_12.imageset/image_12.jpg new file mode 100644 index 0000000000..d520b6d80f Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_12.imageset/image_12.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_13.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_13.imageset/Contents.json new file mode 100644 index 0000000000..4eb6baad3b --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_13.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_13.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_13.imageset/image_13.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_13.imageset/image_13.jpg new file mode 100644 index 0000000000..c0232370cd Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_13.imageset/image_13.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_2.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_2.imageset/Contents.json new file mode 100644 index 0000000000..b2536e53de --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_2.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_2.imageset/image_2.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_2.imageset/image_2.jpg new file mode 100644 index 0000000000..175343454d Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_2.imageset/image_2.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_3.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_3.imageset/Contents.json new file mode 100644 index 0000000000..512e735090 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_3.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_3.imageset/image_3.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_3.imageset/image_3.jpg new file mode 100644 index 0000000000..f5398cac79 Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_3.imageset/image_3.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_4.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_4.imageset/Contents.json new file mode 100644 index 0000000000..88b2b7b98a --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_4.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_4.imageset/image_4.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_4.imageset/image_4.jpg new file mode 100644 index 0000000000..2a6fe4c264 Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_4.imageset/image_4.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_5.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_5.imageset/Contents.json new file mode 100644 index 0000000000..1f24c086d9 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_5.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_5.imageset/image_5.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_5.imageset/image_5.jpg new file mode 100644 index 0000000000..4e507b8064 Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_5.imageset/image_5.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_6.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_6.imageset/Contents.json new file mode 100644 index 0000000000..25f33f2acd --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_6.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_6.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_6.imageset/image_6.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_6.imageset/image_6.jpg new file mode 100644 index 0000000000..35fe778b3a Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_6.imageset/image_6.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_7.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_7.imageset/Contents.json new file mode 100644 index 0000000000..5fdd6ba2cf --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_7.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_7.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_7.imageset/image_7.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_7.imageset/image_7.jpg new file mode 100644 index 0000000000..8f5e037722 Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_7.imageset/image_7.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_8.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_8.imageset/Contents.json new file mode 100644 index 0000000000..563d5ba824 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_8.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_8.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_8.imageset/image_8.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_8.imageset/image_8.jpg new file mode 100644 index 0000000000..5651436bb6 Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_8.imageset/image_8.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_9.imageset/Contents.json b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_9.imageset/Contents.json new file mode 100644 index 0000000000..66c1b859b1 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_9.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "image_9.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_9.imageset/image_9.jpg b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_9.imageset/image_9.jpg new file mode 100644 index 0000000000..9fb6e47d3f Binary files /dev/null and b/examples/CustomCollectionView-Swift/Sample/Assets.xcassets/image_9.imageset/image_9.jpg differ diff --git a/examples/CustomCollectionView-Swift/Sample/Base.lproj/LaunchScreen.storyboard b/examples/CustomCollectionView-Swift/Sample/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..fdf3f97d1b --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/CustomCollectionView-Swift/Sample/Base.lproj/Main.storyboard b/examples/CustomCollectionView-Swift/Sample/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..273375fc70 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/CustomCollectionView-Swift/Sample/ImageCellNode.swift b/examples/CustomCollectionView-Swift/Sample/ImageCellNode.swift new file mode 100644 index 0000000000..52f8fdb55f --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/ImageCellNode.swift @@ -0,0 +1,51 @@ +// +// ImageCellNode.swift +// Sample +// +// Created by Rajeev Gupta on 11/9/16. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit +import AsyncDisplayKit + +class ImageCellNode: ASCellNode { + let imageNode = ASImageNode() + required init(with image : UIImage) { + super.init() + imageNode.image = image + self.addSubnode(self.imageNode) + } + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + let imageSize = imageNode.image?.size + print("imageNode= \(imageNode.bounds), image=\(imageSize)") + + var imageRatio: CGFloat = 0.5 + if imageNode.image != nil { + imageRatio = (imageNode.image?.size.height)! / (imageNode.image?.size.width)! + } + + let imagePlace = ASRatioLayoutSpec(ratio: imageRatio, child: imageNode) + + let stackLayout = ASStackLayoutSpec.horizontal() + stackLayout.justifyContent = .start + stackLayout.alignItems = .start + stackLayout.style.flexShrink = 1.0 + stackLayout.children = [imagePlace] + + return ASInsetLayoutSpec(insets: UIEdgeInsets.zero, child: stackLayout) + } + +} diff --git a/examples/CustomCollectionView-Swift/Sample/Info.plist b/examples/CustomCollectionView-Swift/Sample/Info.plist new file mode 100644 index 0000000000..38e98af23d --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/CustomCollectionView-Swift/Sample/MosaicCollectionViewLayout.swift b/examples/CustomCollectionView-Swift/Sample/MosaicCollectionViewLayout.swift new file mode 100644 index 0000000000..a8770d5282 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/MosaicCollectionViewLayout.swift @@ -0,0 +1,256 @@ +// +// MosaicCollectionViewLayout +// Sample +// +// Created by Rajeev Gupta on 11/9/16. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation +import UIKit +import AsyncDisplayKit + +protocol MosaicCollectionViewLayoutDelegate: ASCollectionDelegate { + func collectionView(_ collectionView: UICollectionView, layout: MosaicCollectionViewLayout, originalItemSizeAtIndexPath: IndexPath) -> CGSize +} + +class MosaicCollectionViewLayout: UICollectionViewFlowLayout { + var numberOfColumns: Int + var columnSpacing: CGFloat + var _sectionInset: UIEdgeInsets + var interItemSpacing: UIEdgeInsets + var headerHeight: CGFloat + var _columnHeights: [[CGFloat]]? + var _itemAttributes = [[UICollectionViewLayoutAttributes]]() + var _headerAttributes = [UICollectionViewLayoutAttributes]() + var _allAttributes = [UICollectionViewLayoutAttributes]() + + required override init() { + self.numberOfColumns = 2 + self.columnSpacing = 10.0 + self.headerHeight = 44.0 //viewcontroller + self._sectionInset = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0) + self.interItemSpacing = UIEdgeInsetsMake(10.0, 0, 10.0, 0) + super.init() + self.scrollDirection = .vertical + } + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public var delegate : MosaicCollectionViewLayoutDelegate? + + override func prepare() { + super.prepare() + guard let collectionView = self.collectionView else { return } + + _itemAttributes = [] + _allAttributes = [] + _headerAttributes = [] + _columnHeights = [] + + var top: CGFloat = 0 + + let numberOfSections: NSInteger = collectionView.numberOfSections + + for section in 0 ..< numberOfSections { + let numberOfItems = collectionView.numberOfItems(inSection: section) + + top += _sectionInset.top + + if (headerHeight > 0) { + let headerSize: CGSize = self._headerSizeForSection(section: section) + + let attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: NSIndexPath(row: 0, section: section) as IndexPath) + + attributes.frame = CGRect(x: _sectionInset.left, y: top, width: headerSize.width, height: headerSize.height) + _headerAttributes.append(attributes) + _allAttributes.append(attributes) + top = attributes.frame.maxY + } + + _columnHeights?.append([]) //Adding new Section + for _ in 0 ..< self.numberOfColumns { + self._columnHeights?[section].append(top) + } + + let columnWidth = self._columnWidthForSection(section: section) + _itemAttributes.append([]) + for idx in 0 ..< numberOfItems { + let columnIndex: Int = self._shortestColumnIndexInSection(section: section) + let indexPath = IndexPath(item: idx, section: section) + + let itemSize = self._itemSizeAtIndexPath(indexPath: indexPath); + let xOffset = _sectionInset.left + (columnWidth + columnSpacing) * CGFloat(columnIndex) + let yOffset = _columnHeights![section][columnIndex] + + let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) + + attributes.frame = CGRect(x: xOffset, y: yOffset, width: itemSize.width, height: itemSize.height) + + _columnHeights?[section][columnIndex] = attributes.frame.maxY + interItemSpacing.bottom + + _itemAttributes[section].append(attributes) + _allAttributes.append(attributes) + } + + let columnIndex: Int = self._tallestColumnIndexInSection(section: section) + top = (_columnHeights?[section][columnIndex])! - interItemSpacing.bottom + _sectionInset.bottom + + for idx in 0 ..< _columnHeights![section].count { + _columnHeights![section][idx] = top + } + } + } + + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? + { + var includedAttributes: [UICollectionViewLayoutAttributes] = [] + // Slow search for small batches + for attribute in _allAttributes { + if (attribute.frame.intersects(rect)) { + includedAttributes.append(attribute) + } + } + return includedAttributes + } + + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? + { + guard indexPath.section < _itemAttributes.count, + indexPath.item < _itemAttributes[indexPath.section].count + else { + return nil + } + return _itemAttributes[indexPath.section][indexPath.item] + } + + override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? + { + if (elementKind == UICollectionElementKindSectionHeader) { + return _headerAttributes[indexPath.section] + } + return nil + } + + override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + if (!(self.collectionView?.bounds.size.equalTo(newBounds.size))!) { + return true; + } + return false; + } + + func _widthForSection (section: Int) -> CGFloat + { + return self.collectionView!.bounds.size.width - _sectionInset.left - _sectionInset.right; + } + + func _columnWidthForSection(section: Int) -> CGFloat + { + return (self._widthForSection(section: section) - ((CGFloat(numberOfColumns - 1)) * columnSpacing)) / CGFloat(numberOfColumns) + } + + func _itemSizeAtIndexPath(indexPath: IndexPath) -> CGSize + { + var size = CGSize(width: self._columnWidthForSection(section: indexPath.section), height: 0) + let originalSize = self.delegate!.collectionView(self.collectionView!, layout:self, originalItemSizeAtIndexPath:indexPath) + if (originalSize.height > 0 && originalSize.width > 0) { + size.height = originalSize.height / originalSize.width * size.width + } + return size + } + + func _headerSizeForSection(section: Int) -> CGSize + { + return CGSize(width: self._widthForSection(section: section), height: headerHeight) + } + + override var collectionViewContentSize: CGSize + { + var height: CGFloat = 0 + if ((_columnHeights?.count)! > 0) { + if (_columnHeights?[(_columnHeights?.count)!-1].count)! > 0 { + height = (_columnHeights?[(_columnHeights?.count)!-1][0])! + } + } + return CGSize(width: self.collectionView!.bounds.size.width, height: height) + } + + func _tallestColumnIndexInSection(section: Int) -> Int + { + var index: Int = 0; + var tallestHeight: CGFloat = 0; + _ = _columnHeights?[section].enumerated().map { (idx,height) in + if (height > tallestHeight) { + index = idx; + tallestHeight = height + } + } + return index + } + + func _shortestColumnIndexInSection(section: Int) -> Int + { + var index: Int = 0; + var shortestHeight: CGFloat = CGFloat.greatestFiniteMagnitude + _ = _columnHeights?[section].enumerated().map { (idx,height) in + if (height < shortestHeight) { + index = idx; + shortestHeight = height + } + } + return index + } + +} + +class MosaicCollectionViewLayoutInspector: NSObject, ASCollectionViewLayoutInspecting +{ + func collectionView(_ collectionView: ASCollectionView, constrainedSizeForNodeAt indexPath: IndexPath) -> ASSizeRange { + let layout = collectionView.collectionViewLayout as! MosaicCollectionViewLayout + return ASSizeRangeMake(CGSize.zero, layout._itemSizeAtIndexPath(indexPath: indexPath)) + } + + func collectionView(_ collectionView: ASCollectionView, constrainedSizeForSupplementaryNodeOfKind: String, at atIndexPath: IndexPath) -> ASSizeRange + { + let layout = collectionView.collectionViewLayout as! MosaicCollectionViewLayout + return ASSizeRange.init(min: CGSize.zero, max: layout._headerSizeForSection(section: atIndexPath.section)) + } + + /** + * Asks the inspector for the number of supplementary sections in the collection view for the given kind. + */ + func collectionView(_ collectionView: ASCollectionView, numberOfSectionsForSupplementaryNodeOfKind kind: String) -> UInt { + if (kind == UICollectionElementKindSectionHeader) { + return UInt((collectionView.dataSource?.numberOfSections!(in: collectionView))!) + } else { + return 0 + } + } + + /** + * Asks the inspector for the number of supplementary views for the given kind in the specified section. + */ + func collectionView(_ collectionView: ASCollectionView, supplementaryNodesOfKind kind: String, inSection section: UInt) -> UInt { + if (kind == UICollectionElementKindSectionHeader) { + return 1 + } else { + return 0 + } + } + + func scrollableDirections() -> ASScrollDirection { + return ASScrollDirectionVerticalDirections; + } +} diff --git a/examples/CustomCollectionView-Swift/Sample/ViewController.swift b/examples/CustomCollectionView-Swift/Sample/ViewController.swift new file mode 100644 index 0000000000..c325f8bc27 --- /dev/null +++ b/examples/CustomCollectionView-Swift/Sample/ViewController.swift @@ -0,0 +1,101 @@ +// +// ViewController.swift +// Sample +// +// Created by Rajeev Gupta on 11/9/16. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit +import AsyncDisplayKit + +class ViewController: UIViewController, MosaicCollectionViewLayoutDelegate, ASCollectionDataSource, ASCollectionDelegate { + + var _sections = [[UIImage]]() + let _collectionNode: ASCollectionNode! + let _layoutInspector = MosaicCollectionViewLayoutInspector() + let kNumberOfImages: UInt = 14 + + required init?(coder aDecoder: NSCoder) { + let layout = MosaicCollectionViewLayout() + layout.numberOfColumns = 3; + layout.headerHeight = 44; + _collectionNode = ASCollectionNode(frame: CGRect.zero, collectionViewLayout: layout) + super.init(coder: aDecoder) + layout.delegate = self + + _sections.append([]); + var section = 0 + for idx in 0 ..< kNumberOfImages { + let name = String(format: "image_%d.jpg", idx) + _sections[section].append(UIImage(named: name)!) + if ((idx + 1) % 5 == 0 && idx < kNumberOfImages - 1) { + section += 1 + _sections.append([]) + } + } + + _collectionNode.dataSource = self; + _collectionNode.delegate = self; + _collectionNode.view.layoutInspector = _layoutInspector + _collectionNode.backgroundColor = UIColor.white + _collectionNode.view.isScrollEnabled = true + _collectionNode.registerSupplementaryNode(ofKind: UICollectionElementKindSectionHeader) + } + + deinit { + _collectionNode.dataSource = nil; + _collectionNode.delegate = nil; + } + + override func viewDidLoad() { + super.viewDidLoad() + self.view.addSubnode(_collectionNode!) + } + + override func viewWillLayoutSubviews() { + _collectionNode.frame = self.view.bounds; + } + + func collectionNode(_ collectionNode: ASCollectionNode, nodeForItemAt indexPath: IndexPath) -> ASCellNode { + let image = _sections[indexPath.section][indexPath.item] + return ImageCellNode(with: image) + } + + + func collectionNode(_ collectionNode: ASCollectionNode, nodeForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> ASCellNode { + let textAttributes : NSDictionary = [ + NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline), + NSForegroundColorAttributeName: UIColor.gray + ] + let textInsets = UIEdgeInsets(top: 11, left: 0, bottom: 11, right: 0) + let textCellNode = ASTextCellNode(attributes: textAttributes as! [AnyHashable : Any], insets: textInsets) + textCellNode.text = String(format: "Section %zd", indexPath.section + 1) + return textCellNode; + } + + + func numberOfSections(in collectionNode: ASCollectionNode) -> Int { + return _sections.count + } + + func collectionNode(_ collectionNode: ASCollectionNode, numberOfItemsInSection section: Int) -> Int { + return _sections[section].count + } + + internal func collectionView(_ collectionView: UICollectionView, layout: MosaicCollectionViewLayout, originalItemSizeAtIndexPath: IndexPath) -> CGSize { + return _sections[originalItemSizeAtIndexPath.section][originalItemSizeAtIndexPath.item].size + } +} + diff --git a/examples/CustomCollectionView/Podfile b/examples/CustomCollectionView/Podfile index 919de4b311..defaf55058 100644 --- a/examples/CustomCollectionView/Podfile +++ b/examples/CustomCollectionView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj index 3aadc478a2..d443a206ff 100644 --- a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj @@ -1,790 +1,381 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 25A1FA831C02F7AC00193875 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - MosaicCollectionViewLayout.h - sourceTree - <group> - - 25A1FA841C02F7AC00193875 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - MosaicCollectionViewLayout.m - sourceTree - <group> - - 25A1FA851C02F7AC00193875 - - fileRef - 25A1FA841C02F7AC00193875 - isa - PBXBuildFile - - 25A1FA861C02FCB000193875 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ImageCellNode.h - sourceTree - <group> - - 25A1FA871C02FCB000193875 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ImageCellNode.m - sourceTree - <group> - - 25A1FA881C02FCB000193875 - - fileRef - 25A1FA871C02FCB000193875 - isa - PBXBuildFile - - 3760AAE3843D6EA89A9A166B - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - 4CC0FB9EE0030992E8FBC0A0 - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - 576F970133B34DFD583D5CE4 - - fileRef - 4CC0FB9EE0030992E8FBC0A0 - isa - PBXBuildFile - - 90A2B9C5397C46134C8A793B - - children - - F36BCD8EBAF79797AB5C6708 - E2F287D91FFDEA2A747630CE - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 9BA2CEA01BB2579C00D18414 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - file.storyboard - path - Launchboard.storyboard - sourceTree - <group> - - 9BA2CEA11BB2579C00D18414 - - fileRef - 9BA2CEA01BB2579C00D18414 - isa - PBXBuildFile - - A6902C454C7661D0D277AC62 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - AC3C4A551A11F47200143C57 - - children - - AC3C4A601A11F47200143C57 - AC3C4A5F1A11F47200143C57 - 90A2B9C5397C46134C8A793B - D6E38FF0CB18E3F55CF06437 - - isa - PBXGroup - sourceTree - <group> - - AC3C4A561A11F47200143C57 - - attributes - - LastUpgradeCheck - 0610 - ORGANIZATIONNAME - Facebook - TargetAttributes - - AC3C4A5D1A11F47200143C57 - - CreatedOnToolsVersion - 6.1 - - - - buildConfigurationList - AC3C4A591A11F47200143C57 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - AC3C4A551A11F47200143C57 - productRefGroup - AC3C4A5F1A11F47200143C57 - projectDirPath - - projectReferences - - projectRoot - - targets - - AC3C4A5D1A11F47200143C57 - - - AC3C4A591A11F47200143C57 - - buildConfigurations - - AC3C4A7F1A11F47200143C57 - AC3C4A801A11F47200143C57 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - AC3C4A5A1A11F47200143C57 - - buildActionMask - 2147483647 - files - - 25A1FA851C02F7AC00193875 - AC3C4A6A1A11F47200143C57 - AC3C4A671A11F47200143C57 - AC3C4A641A11F47200143C57 - 25A1FA881C02FCB000193875 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - AC3C4A5B1A11F47200143C57 - - buildActionMask - 2147483647 - files - - 576F970133B34DFD583D5CE4 - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - AC3C4A5C1A11F47200143C57 - - buildActionMask - 2147483647 - files - - 9BA2CEA11BB2579C00D18414 - AC3C4A8E1A11F80C00143C57 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - AC3C4A5D1A11F47200143C57 - - buildConfigurationList - AC3C4A811A11F47200143C57 - buildPhases - - F868CFBB21824CC9521B6588 - AC3C4A5A1A11F47200143C57 - AC3C4A5B1A11F47200143C57 - AC3C4A5C1A11F47200143C57 - A6902C454C7661D0D277AC62 - 3760AAE3843D6EA89A9A166B - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - AC3C4A5E1A11F47200143C57 - productType - com.apple.product-type.application - - AC3C4A5E1A11F47200143C57 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - AC3C4A5F1A11F47200143C57 - - children - - AC3C4A5E1A11F47200143C57 - - isa - PBXGroup - name - Products - sourceTree - <group> - - AC3C4A601A11F47200143C57 - - children - - 25A1FA831C02F7AC00193875 - 25A1FA841C02F7AC00193875 - AC3C4A651A11F47200143C57 - AC3C4A661A11F47200143C57 - AC3C4A681A11F47200143C57 - AC3C4A691A11F47200143C57 - 25A1FA861C02FCB000193875 - 25A1FA871C02FCB000193875 - AC3C4A8D1A11F80C00143C57 - AC3C4A611A11F47200143C57 - - indentWidth - 2 - isa - PBXGroup - path - Sample - sourceTree - <group> - tabWidth - 2 - usesTabs - 0 - - AC3C4A611A11F47200143C57 - - children - - AC3C4A621A11F47200143C57 - AC3C4A631A11F47200143C57 - 9BA2CEA01BB2579C00D18414 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - AC3C4A621A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - AC3C4A631A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - AC3C4A641A11F47200143C57 - - fileRef - AC3C4A631A11F47200143C57 - isa - PBXBuildFile - - AC3C4A651A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - AC3C4A661A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - AC3C4A671A11F47200143C57 - - fileRef - AC3C4A661A11F47200143C57 - isa - PBXBuildFile - - AC3C4A681A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - AC3C4A691A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - AC3C4A6A1A11F47200143C57 - - fileRef - AC3C4A691A11F47200143C57 - isa - PBXBuildFile - - AC3C4A7F1A11F47200143C57 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_SYMBOLS_PRIVATE_EXTERN - NO - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Debug - - AC3C4A801A11F47200143C57 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - YES - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - TARGETED_DEVICE_FAMILY - 1,2 - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - AC3C4A811A11F47200143C57 - - buildConfigurations - - AC3C4A821A11F47200143C57 - AC3C4A831A11F47200143C57 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - AC3C4A821A11F47200143C57 - - baseConfigurationReference - F36BCD8EBAF79797AB5C6708 - buildSettings - - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME - LaunchImage - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1 - - isa - XCBuildConfiguration - name - Debug - - AC3C4A831A11F47200143C57 - - baseConfigurationReference - E2F287D91FFDEA2A747630CE - buildSettings - - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME - LaunchImage - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1 - - isa - XCBuildConfiguration - name - Release - - AC3C4A8D1A11F80C00143C57 - - isa - PBXFileReference - lastKnownFileType - folder.assetcatalog - path - Images.xcassets - sourceTree - <group> - - AC3C4A8E1A11F80C00143C57 - - fileRef - AC3C4A8D1A11F80C00143C57 - isa - PBXBuildFile - - D6E38FF0CB18E3F55CF06437 - - children - - 4CC0FB9EE0030992E8FBC0A0 - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - E2F287D91FFDEA2A747630CE - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - F36BCD8EBAF79797AB5C6708 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - F868CFBB21824CC9521B6588 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - - rootObject - AC3C4A561A11F47200143C57 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 25A1FA851C02F7AC00193875 /* MosaicCollectionViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */; }; + 25A1FA881C02FCB000193875 /* ImageCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A1FA871C02FCB000193875 /* ImageCellNode.m */; }; + 576F970133B34DFD583D5CE4 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CC0FB9EE0030992E8FBC0A0 /* libPods-Sample.a */; }; + 80364CCA1E3D95A90094400C /* ImageCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 80364CC91E3D95A90094400C /* ImageCollectionViewCell.m */; }; + 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; }; + AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; }; + AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; }; + AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; }; + AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 25A1FA831C02F7AC00193875 /* MosaicCollectionViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MosaicCollectionViewLayout.h; sourceTree = ""; }; + 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MosaicCollectionViewLayout.m; sourceTree = ""; }; + 25A1FA861C02FCB000193875 /* ImageCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageCellNode.h; sourceTree = ""; }; + 25A1FA871C02FCB000193875 /* ImageCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageCellNode.m; sourceTree = ""; }; + 4CC0FB9EE0030992E8FBC0A0 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 80364CC81E3D95A90094400C /* ImageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageCollectionViewCell.h; sourceTree = ""; }; + 80364CC91E3D95A90094400C /* ImageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageCollectionViewCell.m; sourceTree = ""; }; + 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Launchboard.storyboard; sourceTree = ""; }; + AC3C4A5E1A11F47200143C57 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + AC3C4A621A11F47200143C57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AC3C4A631A11F47200143C57 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + AC3C4A651A11F47200143C57 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + AC3C4A661A11F47200143C57 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + AC3C4A681A11F47200143C57 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + AC3C4A691A11F47200143C57 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + E2F287D91FFDEA2A747630CE /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + F36BCD8EBAF79797AB5C6708 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + AC3C4A5B1A11F47200143C57 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 576F970133B34DFD583D5CE4 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 90A2B9C5397C46134C8A793B /* Pods */ = { + isa = PBXGroup; + children = ( + F36BCD8EBAF79797AB5C6708 /* Pods-Sample.debug.xcconfig */, + E2F287D91FFDEA2A747630CE /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + AC3C4A551A11F47200143C57 = { + isa = PBXGroup; + children = ( + AC3C4A601A11F47200143C57 /* Sample */, + AC3C4A5F1A11F47200143C57 /* Products */, + 90A2B9C5397C46134C8A793B /* Pods */, + D6E38FF0CB18E3F55CF06437 /* Frameworks */, + ); + sourceTree = ""; + }; + AC3C4A5F1A11F47200143C57 /* Products */ = { + isa = PBXGroup; + children = ( + AC3C4A5E1A11F47200143C57 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + AC3C4A601A11F47200143C57 /* Sample */ = { + isa = PBXGroup; + children = ( + 25A1FA831C02F7AC00193875 /* MosaicCollectionViewLayout.h */, + 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */, + AC3C4A651A11F47200143C57 /* AppDelegate.h */, + AC3C4A661A11F47200143C57 /* AppDelegate.m */, + AC3C4A681A11F47200143C57 /* ViewController.h */, + AC3C4A691A11F47200143C57 /* ViewController.m */, + 80364CC81E3D95A90094400C /* ImageCollectionViewCell.h */, + 80364CC91E3D95A90094400C /* ImageCollectionViewCell.m */, + 25A1FA861C02FCB000193875 /* ImageCellNode.h */, + 25A1FA871C02FCB000193875 /* ImageCellNode.m */, + AC3C4A8D1A11F80C00143C57 /* Images.xcassets */, + AC3C4A611A11F47200143C57 /* Supporting Files */, + ); + indentWidth = 2; + path = Sample; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + AC3C4A611A11F47200143C57 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + AC3C4A621A11F47200143C57 /* Info.plist */, + AC3C4A631A11F47200143C57 /* main.m */, + 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D6E38FF0CB18E3F55CF06437 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4CC0FB9EE0030992E8FBC0A0 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + AC3C4A5D1A11F47200143C57 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + F868CFBB21824CC9521B6588 /* [CP] Check Pods Manifest.lock */, + AC3C4A5A1A11F47200143C57 /* Sources */, + AC3C4A5B1A11F47200143C57 /* Frameworks */, + AC3C4A5C1A11F47200143C57 /* Resources */, + A6902C454C7661D0D277AC62 /* [CP] Copy Pods Resources */, + 3760AAE3843D6EA89A9A166B /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = AC3C4A5E1A11F47200143C57 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + AC3C4A561A11F47200143C57 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + AC3C4A5D1A11F47200143C57 = { + CreatedOnToolsVersion = 6.1; + }; + }; + }; + buildConfigurationList = AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = AC3C4A551A11F47200143C57; + productRefGroup = AC3C4A5F1A11F47200143C57 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + AC3C4A5D1A11F47200143C57 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + AC3C4A5C1A11F47200143C57 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */, + AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3760AAE3843D6EA89A9A166B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + A6902C454C7661D0D277AC62 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + F868CFBB21824CC9521B6588 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + AC3C4A5A1A11F47200143C57 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 25A1FA851C02F7AC00193875 /* MosaicCollectionViewLayout.m in Sources */, + AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */, + AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */, + AC3C4A641A11F47200143C57 /* main.m in Sources */, + 80364CCA1E3D95A90094400C /* ImageCollectionViewCell.m in Sources */, + 25A1FA881C02FCB000193875 /* ImageCellNode.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + AC3C4A7F1A11F47200143C57 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AC3C4A801A11F47200143C57 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + AC3C4A821A11F47200143C57 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F36BCD8EBAF79797AB5C6708 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + AC3C4A831A11F47200143C57 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E2F287D91FFDEA2A747630CE /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AC3C4A7F1A11F47200143C57 /* Debug */, + AC3C4A801A11F47200143C57 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AC3C4A821A11F47200143C57 /* Debug */, + AC3C4A831A11F47200143C57 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = AC3C4A561A11F47200143C57 /* Project object */; +} diff --git a/examples/CustomCollectionView/Sample/ImageCellNode.h b/examples/CustomCollectionView/Sample/ImageCellNode.h index 6a0f4c63d9..970503c00d 100644 --- a/examples/CustomCollectionView/Sample/ImageCellNode.h +++ b/examples/CustomCollectionView/Sample/ImageCellNode.h @@ -22,5 +22,6 @@ @interface ImageCellNode : ASCellNode - (instancetype)initWithImage:(UIImage *)image; +@property (nonatomic, strong) UIImage *image; @end diff --git a/examples/CustomCollectionView/Sample/ImageCellNode.m b/examples/CustomCollectionView/Sample/ImageCellNode.m index 73b1d373a4..722d8def0d 100644 --- a/examples/CustomCollectionView/Sample/ImageCellNode.m +++ b/examples/CustomCollectionView/Sample/ImageCellNode.m @@ -39,4 +39,14 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:_imageNode]; } +- (void)setImage:(UIImage *)image +{ + _imageNode.image = image; +} + +- (UIImage *)image +{ + return _imageNode.image; +} + @end diff --git a/examples/CustomCollectionView/Sample/ImageCollectionViewCell.h b/examples/CustomCollectionView/Sample/ImageCollectionViewCell.h new file mode 100644 index 0000000000..8ca69a4239 --- /dev/null +++ b/examples/CustomCollectionView/Sample/ImageCollectionViewCell.h @@ -0,0 +1,13 @@ +// +// ImageCollectionViewCell.h +// Sample +// +// Created by Hannah Troisi on 1/28/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +@interface ImageCollectionViewCell : UICollectionViewCell + +@end diff --git a/examples/CustomCollectionView/Sample/ImageCollectionViewCell.m b/examples/CustomCollectionView/Sample/ImageCollectionViewCell.m new file mode 100644 index 0000000000..9ef4a66281 --- /dev/null +++ b/examples/CustomCollectionView/Sample/ImageCollectionViewCell.m @@ -0,0 +1,46 @@ +// +// ImageCollectionViewCell.m +// Sample +// +// Created by Hannah Troisi on 1/28/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "ImageCollectionViewCell.h" + +@implementation ImageCollectionViewCell +{ + UILabel *_title; + UILabel *_description; +} + +- (id)initWithFrame:(CGRect)aRect +{ + self = [super initWithFrame:aRect]; + if (self) { + _title = [[UILabel alloc] init]; + _title.text = @"UICollectionViewCell"; + [self.contentView addSubview:_title]; + + _description = [[UILabel alloc] init]; + _description.text = @"description for cell"; + [self.contentView addSubview:_description]; + + self.contentView.backgroundColor = [UIColor orangeColor]; + } + return self; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + [_title sizeToFit]; + [_description sizeToFit]; + + CGRect frame = _title.frame; + frame.origin.y = _title.frame.size.height; + _description.frame = frame; +} + +@end diff --git a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.h b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.h index 404e55d0a0..82649f7363 100644 --- a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.h +++ b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.h @@ -28,6 +28,9 @@ @property (assign, nonatomic) UIEdgeInsets interItemSpacing; @property (assign, nonatomic) CGFloat headerHeight; +- (CGSize)itemSizeAtIndexPath:(NSIndexPath *)indexPath; +- (CGSize)headerSizeForSection:(NSInteger)section; + @end @protocol MosaicCollectionViewLayoutDelegate @@ -35,7 +38,3 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(MosaicCollectionViewLayout *)layout originalItemSizeAtIndexPath:(NSIndexPath *)indexPath; @end - -@interface MosaicCollectionViewLayoutInspector : NSObject - -@end diff --git a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m index 7396de226d..633fd0fc67 100644 --- a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m +++ b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m @@ -54,7 +54,7 @@ - (void)prepareLayout top += _sectionInset.top; if (_headerHeight > 0) { - CGSize headerSize = [self _headerSizeForSection:section]; + CGSize headerSize = [self headerSizeForSection:section]; UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; @@ -75,7 +75,7 @@ - (void)prepareLayout NSUInteger columnIndex = [self _shortestColumnIndexInSection:section]; NSIndexPath *indexPath = [NSIndexPath indexPathForItem:idx inSection:section]; - CGSize itemSize = [self _itemSizeAtIndexPath:indexPath]; + CGSize itemSize = [self itemSizeAtIndexPath:indexPath]; CGFloat xOffset = _sectionInset.left + (columnWidth + _columnSpacing) * columnIndex; CGFloat yOffset = [_columnHeights[section][columnIndex] floatValue]; @@ -146,7 +146,7 @@ - (CGFloat)_columnWidthForSection:(NSUInteger)section return ([self _widthForSection:section] - ((_numberOfColumns - 1) * _columnSpacing)) / _numberOfColumns; } -- (CGSize)_itemSizeAtIndexPath:(NSIndexPath *)indexPath +- (CGSize)itemSizeAtIndexPath:(NSIndexPath *)indexPath { CGSize size = CGSizeMake([self _columnWidthForSection:indexPath.section], 0); CGSize originalSize = [[self _delegate] collectionView:self.collectionView layout:self originalItemSizeAtIndexPath:indexPath]; @@ -156,7 +156,7 @@ - (CGSize)_itemSizeAtIndexPath:(NSIndexPath *)indexPath return size; } -- (CGSize)_headerSizeForSection:(NSUInteger)section +- (CGSize)headerSizeForSection:(NSInteger)section { return CGSizeMake([self _widthForSection:section], _headerHeight); } @@ -199,36 +199,3 @@ - (NSUInteger)_shortestColumnIndexInSection:(NSUInteger)section } @end - -@implementation MosaicCollectionViewLayoutInspector - -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - MosaicCollectionViewLayout *layout = (MosaicCollectionViewLayout *)[collectionView collectionViewLayout]; - return ASSizeRangeMake(CGSizeZero, [layout _itemSizeAtIndexPath:indexPath]); -} - -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - MosaicCollectionViewLayout *layout = (MosaicCollectionViewLayout *)[collectionView collectionViewLayout]; - return ASSizeRangeMake(CGSizeZero, [layout _headerSizeForSection:indexPath.section]); -} - -- (ASScrollDirection)scrollableDirections -{ - return ASScrollDirectionVerticalDirections; -} - -/** - * Asks the inspector for the number of supplementary views for the given kind in the specified section. - */ -- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section -{ - if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { - return 1; - } else { - return 0; - } -} - -@end diff --git a/examples/CustomCollectionView/Sample/ViewController.m b/examples/CustomCollectionView/Sample/ViewController.m index 5d070ebcd6..e9e59f6b35 100644 --- a/examples/CustomCollectionView/Sample/ViewController.m +++ b/examples/CustomCollectionView/Sample/ViewController.m @@ -20,14 +20,17 @@ #import #import "MosaicCollectionViewLayout.h" #import "ImageCellNode.h" +#import "ImageCollectionViewCell.h" +// This option demonstrates that raw UIKit cells can still be used alongside native ASCellNodes. +static BOOL kShowUICollectionViewCells = YES; +static NSString *kReuseIdentifier = @"ImageCollectionViewCell"; static NSUInteger kNumberOfImages = 14; -@interface ViewController () +@interface ViewController () { NSMutableArray *_sections; ASCollectionNode *_collectionNode; - MosaicCollectionViewLayoutInspector *_layoutInspector; } @end @@ -35,7 +38,7 @@ @interface ViewController () @implementation ViewController #pragma mark - -#pragma mark UIViewController. +#pragma mark UIViewController - (instancetype)init { @@ -48,8 +51,6 @@ - (instancetype)init _collectionNode.delegate = self; _collectionNode.backgroundColor = [UIColor whiteColor]; - _layoutInspector = [[MosaicCollectionViewLayoutInspector alloc] init]; - if (!(self = [super initWithNode:_collectionNode])) return nil; @@ -73,7 +74,8 @@ - (void)viewDidLoad { [super viewDidLoad]; - _collectionNode.view.layoutInspector = _layoutInspector; + _collectionNode.view.layoutInspector = self; + [_collectionNode.view registerClass:[ImageCollectionViewCell class] forCellWithReuseIdentifier:kReuseIdentifier]; } - (void)reloadTapped @@ -85,6 +87,11 @@ - (void)reloadTapped - (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { + if (kShowUICollectionViewCells && indexPath.item % 3 == 1) { + // When enabled, return nil for every third cell and then cellForItemAtIndexPath: will be called. + return nil; + } + UIImage *image = _sections[indexPath.section][indexPath.item]; return ^{ return [[ImageCellNode alloc] initWithImage:image]; @@ -92,6 +99,31 @@ - (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockFo } +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath +{ + MosaicCollectionViewLayout *layout = (MosaicCollectionViewLayout *)[collectionView collectionViewLayout]; + return ASSizeRangeMake(CGSizeZero, [layout itemSizeAtIndexPath:indexPath]); +} + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + MosaicCollectionViewLayout *layout = (MosaicCollectionViewLayout *)[collectionView collectionViewLayout]; + return ASSizeRangeMake(CGSizeZero, [layout headerSizeForSection:indexPath.section]); +} + +- (ASScrollDirection)scrollableDirections +{ + return ASScrollDirectionVerticalDirections; +} + +/** + * Asks the inspector for the number of supplementary views for the given kind in the specified section. + */ +- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section +{ + return [kind isEqualToString:UICollectionElementKindSectionHeader] ? 1 : 0; +} + - (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { NSDictionary *textAttributes = @{ @@ -116,7 +148,22 @@ - (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSe - (CGSize)collectionView:(ASCollectionNode *)collectionNode layout:(UICollectionViewLayout *)collectionViewLayout originalItemSizeAtIndexPath:(NSIndexPath *)indexPath { - return [(UIImage *)_sections[indexPath.section][indexPath.item] size]; + ASCellNode *cellNode = [collectionNode nodeForItemAtIndexPath:indexPath]; + if ([cellNode isKindOfClass:[ImageCellNode class]]) { + return [[(ImageCellNode *)cellNode image] size]; + } else { + return CGSizeMake(100, 100); // In kShowUICollectionViewCells = YES mode, make those cells 100x100. + } +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath +{ + return [_collectionNode.view dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + return nil; } @end diff --git a/examples/HorizontalWithinVerticalScrolling/Podfile b/examples/HorizontalWithinVerticalScrolling/Podfile index 919de4b311..defaf55058 100644 --- a/examples/HorizontalWithinVerticalScrolling/Podfile +++ b/examples/HorizontalWithinVerticalScrolling/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj b/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj index 00f893b725..c999679905 100644 --- a/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj +++ b/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj @@ -1,806 +1,375 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 05561CFB19D4F94A00CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - HorizontalScrollCellNode.h - sourceTree - <group> - - 05561CFC19D4F94A00CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.cpp.objcpp - path - HorizontalScrollCellNode.mm - sourceTree - <group> - - 05561CFD19D4F94A00CBA93C - - fileRef - 05561CFC19D4F94A00CBA93C - isa - PBXBuildFile - - 0585427F19D4DBE100606EA6 - - isa - PBXFileReference - lastKnownFileType - image.png - name - Default-568h@2x.png - path - ../Default-568h@2x.png - sourceTree - <group> - - 0585428019D4DBE100606EA6 - - fileRef - 0585427F19D4DBE100606EA6 - isa - PBXBuildFile - - 05E2127819D4DB510098F589 - - children - - 05E2128319D4DB510098F589 - 05E2128219D4DB510098F589 - 1A943BF0259746F18D6E423F - 1AE410B73DA5C3BD087ACDD7 - - indentWidth - 2 - isa - PBXGroup - sourceTree - <group> - tabWidth - 2 - usesTabs - 0 - - 05E2127919D4DB510098F589 - - attributes - - LastUpgradeCheck - 0600 - ORGANIZATIONNAME - Facebook - TargetAttributes - - 05E2128019D4DB510098F589 - - CreatedOnToolsVersion - 6.0.1 - - - - buildConfigurationList - 05E2127C19D4DB510098F589 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - 05E2127819D4DB510098F589 - productRefGroup - 05E2128219D4DB510098F589 - projectDirPath - - projectReferences - - projectRoot - - targets - - 05E2128019D4DB510098F589 - - - 05E2127C19D4DB510098F589 - - buildConfigurations - - 05E212A219D4DB510098F589 - 05E212A319D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E2127D19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 18C2ED861B9B8CE700F627B3 - 05561CFD19D4F94A00CBA93C - 05E2128D19D4DB510098F589 - 05E2128A19D4DB510098F589 - 05E2128719D4DB510098F589 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127E19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 18B5AC4A1550AC957426B54E - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127F19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 0585428019D4DBE100606EA6 - 6C2C82AC19EE274300767484 - 6C2C82AD19EE274300767484 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2128019D4DB510098F589 - - buildConfigurationList - 05E212A419D4DB510098F589 - buildPhases - - E080B80F89C34A25B3488E26 - 05E2127D19D4DB510098F589 - 05E2127E19D4DB510098F589 - 05E2127F19D4DB510098F589 - F012A6F39E0149F18F564F50 - A79A9172A45D7C9595AA01CC - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - 05E2128119D4DB510098F589 - productType - com.apple.product-type.application - - 05E2128119D4DB510098F589 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - 05E2128219D4DB510098F589 - - children - - 05E2128119D4DB510098F589 - - isa - PBXGroup - name - Products - sourceTree - <group> - - 05E2128319D4DB510098F589 - - children - - 05E2128819D4DB510098F589 - 05E2128919D4DB510098F589 - 05E2128B19D4DB510098F589 - 05E2128C19D4DB510098F589 - 05561CFB19D4F94A00CBA93C - 05561CFC19D4F94A00CBA93C - 18C2ED841B9B8CE700F627B3 - 18C2ED851B9B8CE700F627B3 - 05E2128419D4DB510098F589 - - isa - PBXGroup - path - Sample - sourceTree - <group> - - 05E2128419D4DB510098F589 - - children - - 0585427F19D4DBE100606EA6 - 6C2C82AA19EE274300767484 - 6C2C82AB19EE274300767484 - 05E2128519D4DB510098F589 - 05E2128619D4DB510098F589 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - 05E2128519D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - 05E2128619D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - 05E2128719D4DB510098F589 - - fileRef - 05E2128619D4DB510098F589 - isa - PBXBuildFile - - 05E2128819D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - 05E2128919D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - 05E2128A19D4DB510098F589 - - fileRef - 05E2128919D4DB510098F589 - isa - PBXBuildFile - - 05E2128B19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - 05E2128C19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - 05E2128D19D4DB510098F589 - - fileRef - 05E2128C19D4DB510098F589 - isa - PBXBuildFile - - 05E212A219D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_SYMBOLS_PRIVATE_EXTERN - NO - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - - isa - XCBuildConfiguration - name - Debug - - 05E212A319D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - YES - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - 05E212A419D4DB510098F589 - - buildConfigurations - - 05E212A519D4DB510098F589 - 05E212A619D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E212A519D4DB510098F589 - - baseConfigurationReference - 367E401FD4A0E65C4C240050 - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Debug - - 05E212A619D4DB510098F589 - - baseConfigurationReference - 9CEC783A48902CC0051FDE7E - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Release - - 18B5AC4A1550AC957426B54E - - fileRef - 9341ADE7BE83CA50F1FD55C1 - isa - PBXBuildFile - - 18C2ED841B9B8CE700F627B3 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - RandomCoreGraphicsNode.h - sourceTree - <group> - - 18C2ED851B9B8CE700F627B3 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - RandomCoreGraphicsNode.m - sourceTree - <group> - - 18C2ED861B9B8CE700F627B3 - - fileRef - 18C2ED851B9B8CE700F627B3 - isa - PBXBuildFile - - 1A943BF0259746F18D6E423F - - children - - 9341ADE7BE83CA50F1FD55C1 - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - 1AE410B73DA5C3BD087ACDD7 - - children - - 367E401FD4A0E65C4C240050 - 9CEC783A48902CC0051FDE7E - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 367E401FD4A0E65C4C240050 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - 6C2C82AA19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-667h@2x.png - sourceTree - SOURCE_ROOT - - 6C2C82AB19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-736h@3x.png - sourceTree - SOURCE_ROOT - - 6C2C82AC19EE274300767484 - - fileRef - 6C2C82AA19EE274300767484 - isa - PBXBuildFile - - 6C2C82AD19EE274300767484 - - fileRef - 6C2C82AB19EE274300767484 - isa - PBXBuildFile - - 9341ADE7BE83CA50F1FD55C1 - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - 9CEC783A48902CC0051FDE7E - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - A79A9172A45D7C9595AA01CC - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - E080B80F89C34A25B3488E26 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - F012A6F39E0149F18F564F50 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - - rootObject - 05E2127919D4DB510098F589 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 05561CFD19D4F94A00CBA93C /* HorizontalScrollCellNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05561CFC19D4F94A00CBA93C /* HorizontalScrollCellNode.mm */; }; + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 18B5AC4A1550AC957426B54E /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9341ADE7BE83CA50F1FD55C1 /* libPods-Sample.a */; }; + 18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 05561CFB19D4F94A00CBA93C /* HorizontalScrollCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HorizontalScrollCellNode.h; sourceTree = ""; }; + 05561CFC19D4F94A00CBA93C /* HorizontalScrollCellNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = HorizontalScrollCellNode.mm; sourceTree = ""; }; + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RandomCoreGraphicsNode.h; sourceTree = ""; }; + 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RandomCoreGraphicsNode.m; sourceTree = ""; }; + 367E401FD4A0E65C4C240050 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + 9341ADE7BE83CA50F1FD55C1 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9CEC783A48902CC0051FDE7E /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 18B5AC4A1550AC957426B54E /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + 05561CFB19D4F94A00CBA93C /* HorizontalScrollCellNode.h */, + 05561CFC19D4F94A00CBA93C /* HorizontalScrollCellNode.mm */, + 18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */, + 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 9341ADE7BE83CA50F1FD55C1 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + 367E401FD4A0E65C4C240050 /* Pods-Sample.debug.xcconfig */, + 9CEC783A48902CC0051FDE7E /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + A79A9172A45D7C9595AA01CC /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0600; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + A79A9172A45D7C9595AA01CC /* 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-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */, + 05561CFD19D4F94A00CBA93C /* HorizontalScrollCellNode.mm in Sources */, + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 367E401FD4A0E65C4C240050 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9CEC783A48902CC0051FDE7E /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples/Kittens/Podfile b/examples/Kittens/Podfile index 919de4b311..defaf55058 100644 --- a/examples/Kittens/Podfile +++ b/examples/Kittens/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/Kittens/Sample.xcodeproj/project.pbxproj b/examples/Kittens/Sample.xcodeproj/project.pbxproj index a5dffda444..7e5f1a9ef9 100644 --- a/examples/Kittens/Sample.xcodeproj/project.pbxproj +++ b/examples/Kittens/Sample.xcodeproj/project.pbxproj @@ -333,7 +333,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -347,7 +346,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index 67e586b4a7..db215cb40b 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -88,10 +88,11 @@ - (instancetype)initWithKittenOfSize:(CGSize)size // kitten image, with a solid background colour serving as placeholder _imageNode = [[ASNetworkImageNode alloc] init]; - _imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); _imageNode.URL = [NSURL URLWithString:[NSString stringWithFormat:@"https://placekitten.com/%zd/%zd", (NSInteger)roundl(_kittenSize.width), (NSInteger)roundl(_kittenSize.height)]]; + _imageNode.placeholderFadeDuration = .5; + _imageNode.placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); // _imageNode.contentMode = UIViewContentModeCenter; [_imageNode addTarget:self action:@selector(toggleNodesSwap) forControlEvents:ASControlNodeEventTouchUpInside]; [self addSubnode:_imageNode]; diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 6b2e4b5058..63a662327a 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -57,8 +57,6 @@ - (instancetype)init if (!(self = [super initWithNode:_tableNode])) return nil; - - _tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; // KittenNode has its own separator // populate our "data source" with some random kittens _kittenDataSource = [self createLitterWithSize:kLitterSize]; @@ -76,6 +74,7 @@ - (void)viewDidLoad { [super viewDidLoad]; + _tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; // KittenNode has its own separator [self.node addSubnode:_tableNode]; } diff --git a/examples/LayoutSpecExamples-Swift/Podfile b/examples/LayoutSpecExamples-Swift/Podfile index 628b25d36c..4013adc2f2 100644 --- a/examples/LayoutSpecExamples-Swift/Podfile +++ b/examples/LayoutSpecExamples-Swift/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' use_frameworks! diff --git a/examples/LayoutSpecExamples/Podfile b/examples/LayoutSpecExamples/Podfile index 5c30ce798e..7a8d8c1a00 100644 --- a/examples/LayoutSpecExamples/Podfile +++ b/examples/LayoutSpecExamples/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj b/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj index 076d8ed2d4..8027131811 100644 --- a/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj +++ b/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj @@ -296,7 +296,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -333,7 +333,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/LayoutSpecExamples/Sample.xcworkspace/contents.xcworkspacedata b/examples/LayoutSpecExamples/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7b5a2f3050..0000000000 --- a/examples/LayoutSpecExamples/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/LayoutSpecPlayground/Sample.xcworkspace/contents.xcworkspacedata b/examples/LayoutSpecPlayground/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7b5a2f3050..0000000000 --- a/examples/LayoutSpecPlayground/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/LayoutSpecPlayground/Sample/AppDelegate.h b/examples/LayoutSpecPlayground/Sample/AppDelegate.h deleted file mode 100644 index 8ec4f8f6fe..0000000000 --- a/examples/LayoutSpecPlayground/Sample/AppDelegate.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// AppDelegate.h -// ASLayoutSpecPlayground -// -// Created by Hannah Troisi on 3/11/16. -// Copyright © 2016 Hannah Troisi. All rights reserved. -// - -#import - -@interface AppDelegate : UIResponder - -@property (strong, nonatomic) UIWindow *window; - -@end - diff --git a/examples/LayoutSpecPlayground/Sample/AppDelegate.m b/examples/LayoutSpecPlayground/Sample/AppDelegate.m deleted file mode 100644 index 7b167a8ed1..0000000000 --- a/examples/LayoutSpecPlayground/Sample/AppDelegate.m +++ /dev/null @@ -1,50 +0,0 @@ -// -// AppDelegate.m -// ASLayoutSpecPlayground -// -// Created by Hannah Troisi on 3/11/16. -// Copyright © 2016 Hannah Troisi. All rights reserved. -// - -#import "AppDelegate.h" -#import "ViewController.h" -#import "Utilities.h" -#import - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - - UIViewController *rootVC = nil; - - UIDevice *device = [UIDevice currentDevice]; - if (device.userInterfaceIdiom == UIUserInterfaceIdiomPad) { - - ASViewController *masterVC = [[ASViewController alloc] initWithNode:[ASLayoutElementInspectorNode sharedInstance]]; - masterVC.view.backgroundColor = [UIColor customOrangeColor]; - UINavigationController *masterNav = [[UINavigationController alloc] initWithRootViewController:masterVC]; - - ViewController *detailVC = [[ViewController alloc] init]; - UINavigationController *detailNav = [[UINavigationController alloc] initWithRootViewController:detailVC]; - - UISplitViewController *splitVC = [[UISplitViewController alloc] init]; - splitVC.viewControllers = @[masterNav, detailNav]; - splitVC.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible; - splitVC.maximumPrimaryColumnWidth = 250; - - rootVC = splitVC; - - } else { - // FIXME: make this work for iPhones - NSAssert(YES, @"App optimized for iPad only."); - } - - [self.window setRootViewController:rootVC]; - [self.window makeKeyAndVisible]; - - return YES; -} - -@end diff --git a/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.h b/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.h deleted file mode 100644 index 5ca9987e3c..0000000000 --- a/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// LayoutExampleNodes.h -// Sample -// -// Created by Hannah Troisi on 9/13/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import - -@interface LayoutExampleNode : ASDisplayNode - -- (NSAttributedString *)usernameAttributedStringWithFontSize:(CGFloat)size; -- (NSAttributedString *)locationAttributedStringWithFontSize:(CGFloat)size; -- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size; -- (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size; -- (NSAttributedString *)descriptionAttributedStringWithFontSize:(CGFloat)size; - -@end - -@interface HorizontalStackWithSpacer : LayoutExampleNode - -@property (nonatomic, strong) ASTextNode *usernameNode; -@property (nonatomic, strong) ASTextNode *postLocationNode; -@property (nonatomic, strong) ASTextNode *postTimeNode; - -@end - -@interface PhotoWithInsetTextOverlay : LayoutExampleNode - -@property (nonatomic, strong) ASNetworkImageNode *photoNode; -@property (nonatomic, strong) ASTextNode *titleNode; - -@end - -@interface PhotoWithOutsetIconOverlay : LayoutExampleNode - -@property (nonatomic, strong) ASNetworkImageNode *photoNode; -@property (nonatomic, strong) ASNetworkImageNode *iconNode; - -@end - -@interface FlexibleSeparatorSurroundingContent : LayoutExampleNode - -@property (nonatomic, strong) ASImageNode *topSeparator; -@property (nonatomic, strong) ASImageNode *bottomSeparator; -@property (nonatomic, strong) ASTextNode *textNode; - -@end diff --git a/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.m b/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.m deleted file mode 100644 index 35834ec546..0000000000 --- a/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.m +++ /dev/null @@ -1,272 +0,0 @@ -// -// LayoutExampleNodes.m -// Sample -// -// Created by Hannah Troisi on 9/13/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import "LayoutExampleNodes.h" -#import "Utilities.h" -#import "UIImage+ASConvenience.h" - -#define USER_IMAGE_HEIGHT 60 -#define HORIZONTAL_BUFFER 10 -#define VERTICAL_BUFFER 5 -#define FONT_SIZE 20 - -@implementation LayoutExampleNode - -- (instancetype)init -{ - self = [super init]; - if (self) { - self.automaticallyManagesSubnodes = YES; - self.shouldVisualizeLayoutSpecs = NO; - self.shouldCacheLayoutSpec = NO; - self.backgroundColor = [UIColor whiteColor]; - } - return self; -} - -#pragma mark - helper methods - -- (NSAttributedString *)usernameAttributedStringWithFontSize:(CGFloat)size -{ - return [NSAttributedString attributedStringWithString:@"hannahmbanana" - fontSize:size - color:[UIColor darkBlueColor] - firstWordColor:nil]; -} - -- (NSAttributedString *)locationAttributedStringWithFontSize:(CGFloat)size -{ - return [NSAttributedString attributedStringWithString:@"San Fransisco, CA" - fontSize:size - color:[UIColor lightBlueColor] - firstWordColor:nil]; -} - -- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size -{ - return [NSAttributedString attributedStringWithString:@"30m" - fontSize:size - color:[UIColor lightGrayColor] - firstWordColor:nil]; -} - -- (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size -{ - return [NSAttributedString attributedStringWithString:@"♥︎ 17 likes" - fontSize:size - color:[UIColor darkBlueColor] - firstWordColor:nil]; -} - -- (NSAttributedString *)descriptionAttributedStringWithFontSize:(CGFloat)size -{ - NSString *string = [NSString stringWithFormat:@"hannahmbanana check out this cool pic from the internet!"]; - NSAttributedString *attrString = [NSAttributedString attributedStringWithString:string - fontSize:size - color:[UIColor darkGrayColor] - firstWordColor:[UIColor darkBlueColor]]; - return attrString; -} - -@end - -@implementation HorizontalStackWithSpacer - -- (instancetype)init -{ - self = [super init]; - - if (self) { - _usernameNode = [[ASTextNode alloc] init]; - _usernameNode.attributedText = [self usernameAttributedStringWithFontSize:FONT_SIZE]; - - _postLocationNode = [[ASTextNode alloc] init]; - _postLocationNode.maximumNumberOfLines = 1; - _postLocationNode.attributedText = [self locationAttributedStringWithFontSize:FONT_SIZE]; - - _postTimeNode = [[ASTextNode alloc] init]; - _postTimeNode.attributedText = [self uploadDateAttributedStringWithFontSize:FONT_SIZE]; - } - - return self; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - _usernameNode.style.flexShrink = 1.0; - _postLocationNode.style.flexShrink = 1.0; - - ASStackLayoutSpec *verticalStackSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; - verticalStackSpec.style.flexShrink = 1.0; - - // Example: see ASDKgram for how this technique can be used to animate in the location label - // once a separate network request provides the data. - if (_postLocationNode.attributedText) { - [verticalStackSpec setChildren:@[_usernameNode, _postLocationNode]]; - } else { - [verticalStackSpec setChildren:@[_usernameNode]]; - } - - ASLayoutSpec *spacerSpec = [[ASLayoutSpec alloc] init]; - spacerSpec.style.flexGrow = 1.0; - spacerSpec.style.flexShrink = 1.0; - - // horizontal stack - ASStackLayoutSpec *horizontalStackSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; - horizontalStackSpec.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horiz stack - horizontalStackSpec.justifyContent = ASStackLayoutJustifyContentStart; // justify content to left - horizontalStackSpec.style.flexShrink = 1.0; - horizontalStackSpec.style.flexGrow = 1.0; - [horizontalStackSpec setChildren:@[verticalStackSpec, spacerSpec, _postTimeNode]]; - - // inset horizontal stack - UIEdgeInsets insets = UIEdgeInsetsMake(0, 10, 0, 10); - ASInsetLayoutSpec *headerInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:horizontalStackSpec]; - headerInsetSpec.style.flexShrink = 1.0; - headerInsetSpec.style.flexGrow = 1.0; - - return headerInsetSpec; -} - -@end - - -@implementation PhotoWithInsetTextOverlay - -- (instancetype)init -{ - self = [super init]; - - if (self) { - _photoNode = [[ASNetworkImageNode alloc] init]; - _photoNode.URL = [NSURL URLWithString:@"http://asyncdisplaykit.org/static/images/layout-examples-photo-with-inset-text-overlay-photo.png"]; - - _titleNode = [[ASTextNode alloc] init]; - _titleNode.maximumNumberOfLines = 2; - _titleNode.truncationAttributedText = [NSAttributedString attributedStringWithString:@"..." - fontSize:16 - color:[UIColor whiteColor] - firstWordColor:nil]; - _titleNode.attributedText = [NSAttributedString attributedStringWithString:@"family fall hikes" - fontSize:16 - color:[UIColor whiteColor] - firstWordColor:nil]; - } - - return self; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - _photoNode.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT*2, USER_IMAGE_HEIGHT*2); - - UIEdgeInsets insets = UIEdgeInsetsMake(INFINITY, 12, 12, 12); - ASInsetLayoutSpec *textInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets - child:_titleNode]; - - ASOverlayLayoutSpec *textOverlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_photoNode - overlay:textInsetSpec]; - - return textOverlaySpec; -} - -@end - - -@implementation PhotoWithOutsetIconOverlay - -- (instancetype)init -{ - self = [super init]; - - if (self) { - - _photoNode = [[ASNetworkImageNode alloc] init]; - _photoNode.URL = [NSURL URLWithString:@"http://asyncdisplaykit.org/static/images/layout-examples-photo-with-outset-icon-overlay-photo.png"]; - - _iconNode = [[ASNetworkImageNode alloc] init]; - _iconNode.URL = [NSURL URLWithString:@"http://asyncdisplaykit.org/static/images/layout-examples-photo-with-outset-icon-overlay-icon.png"]; - - [_iconNode setImageModificationBlock:^UIImage *(UIImage *image) { // FIXME: in framework autocomplete for setImageModificationBlock line seems broken - CGSize profileImageSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); - return [image makeCircularImageWithSize:profileImageSize withBorderWidth:10]; - }]; - } - - return self; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - _iconNode.style.preferredSize = CGSizeMake(40, 40); - _photoNode.style.preferredSize = CGSizeMake(150, 150); - - CGFloat x = 150; - CGFloat y = 0; - - _iconNode.style.layoutPosition = CGPointMake(x, y); - _photoNode.style.layoutPosition = CGPointMake(40 / 2.0, 40 / 2.0); - - ASAbsoluteLayoutSpec *absoluteLayoutSpec = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[_photoNode, _iconNode]]; - - return absoluteLayoutSpec; -} - -@end - - -@implementation FlexibleSeparatorSurroundingContent - -- (instancetype)init -{ - self = [super init]; - - if (self) { - - self.backgroundColor = [UIColor cyanColor]; - - _topSeparator = [[ASImageNode alloc] init]; - _topSeparator.image = [UIImage as_resizableRoundedImageWithCornerRadius:1.0 - cornerColor:[UIColor blackColor] - fillColor:[UIColor blackColor]]; - - _textNode = [[ASTextNode alloc] init]; - _textNode.attributedText = [NSAttributedString attributedStringWithString:@"this is a long text node" - fontSize:16 - color:[UIColor blackColor] - firstWordColor:nil]; - - _bottomSeparator = [[ASImageNode alloc] init]; - _bottomSeparator.image = [UIImage as_resizableRoundedImageWithCornerRadius:1.0 - cornerColor:[UIColor blackColor] - fillColor:[UIColor blackColor]]; - } - - return self; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - _topSeparator.style.flexGrow = 1.0; - _bottomSeparator.style.flexGrow = 1.0; - - UIEdgeInsets contentInsets = UIEdgeInsetsMake(10, 10, 10, 10); - ASInsetLayoutSpec *insetContentSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:contentInsets - child:_textNode]; - // final vertical stack - ASStackLayoutSpec *verticalStackSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; - verticalStackSpec.direction = ASStackLayoutDirectionVertical; - verticalStackSpec.justifyContent = ASStackLayoutJustifyContentCenter; - verticalStackSpec.alignItems = ASStackLayoutAlignItemsStretch; - verticalStackSpec.children = @[_topSeparator, insetContentSpec, _bottomSeparator]; - - return verticalStackSpec; -} - -@end - diff --git a/examples/LayoutSpecPlayground/Sample/PhotoPostNode.h b/examples/LayoutSpecPlayground/Sample/PhotoPostNode.h deleted file mode 100644 index 3270ab4935..0000000000 --- a/examples/LayoutSpecPlayground/Sample/PhotoPostNode.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// PhotoPostNode.h -// ASLayoutSpecPlayground -// -// Created by Hannah Troisi on 3/11/16. -// Copyright © 2016 Hannah Troisi. All rights reserved. -// - -#import "AsyncDisplayKit.h" - -@interface PhotoPostNode : ASDisplayNode - -- (instancetype)initWithIndex:(NSUInteger)index; - -@end diff --git a/examples/LayoutSpecPlayground/Sample/PhotoPostNode.m b/examples/LayoutSpecPlayground/Sample/PhotoPostNode.m deleted file mode 100644 index 644aa5fbee..0000000000 --- a/examples/LayoutSpecPlayground/Sample/PhotoPostNode.m +++ /dev/null @@ -1,249 +0,0 @@ -// -// PhotoPostNode.m -// ASLayoutSpecPlayground -// -// Created by Hannah Troisi on 3/11/16. -// Copyright © 2016 Hannah Troisi. All rights reserved. -// - -#import "PhotoPostNode.h" -#import "AsyncDisplayKit+Debug.h" -#import "Utilities.h" - -#define USER_IMAGE_HEIGHT 60 -#define HORIZONTAL_BUFFER 10 -#define VERTICAL_BUFFER 5 -#define FONT_SIZE 20 - -@implementation PhotoPostNode -{ - NSUInteger _index; - ASNetworkImageNode *_userAvatarImageView; - ASNetworkImageNode *_photoImageView; - ASTextNode *_userNameLabel; - ASTextNode *_photoLocationLabel; - ASTextNode *_photoTimeIntervalSincePostLabel; - ASTextNode *_photoLikesLabel; - ASTextNode *_photoDescriptionLabel; -} - -#pragma mark - Lifecycle - -- (instancetype)initWithIndex:(NSUInteger)index -{ - self = [super init]; - - if (self) { - - self.backgroundColor = [UIColor whiteColor]; - self.automaticallyManagesSubnodes = YES; - self.shouldVisualizeLayoutSpecs = YES; - self.shouldCacheLayoutSpec = YES; - - _index = index; - - _userAvatarImageView = [[ASNetworkImageNode alloc] init]; - _userAvatarImageView.URL = [NSURL URLWithString:@"https://s-media-cache-ak0.pinimg.com/avatars/503h_1458880322_140.jpg"]; - - [_userAvatarImageView setImageModificationBlock:^UIImage *(UIImage *image) { // FIXME: in framework autocomplete for setImageModificationBlock line seems broken - CGSize profileImageSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); - return [image makeCircularImageWithSize:profileImageSize withBorderWidth:0]; - }]; - - _userNameLabel = [[ASTextNode alloc] init]; - _userNameLabel.attributedText = [self usernameAttributedStringWithFontSize:FONT_SIZE]; - - _photoLocationLabel = [[ASTextNode alloc] init]; - _photoLocationLabel.maximumNumberOfLines = 1; - _photoLocationLabel.attributedText = [self locationAttributedStringWithFontSize:FONT_SIZE]; - - _photoTimeIntervalSincePostLabel = [[ASTextNode alloc] init]; - _photoTimeIntervalSincePostLabel.attributedText = [self uploadDateAttributedStringWithFontSize:FONT_SIZE]; - - _photoImageView = [[ASNetworkImageNode alloc] init]; - _photoImageView.URL = [NSURL URLWithString:@"https://s-media-cache-ak0.pinimg.com/564x/9f/5b/3a/9f5b3a35640bc7a5d484b66124c48c46.jpg"]; - - _photoLikesLabel = [[ASTextNode alloc] init]; - _photoLikesLabel.attributedText = [self likesAttributedStringWithFontSize:FONT_SIZE]; - - _photoDescriptionLabel = [[ASTextNode alloc] init]; - _photoDescriptionLabel.attributedText = [self descriptionAttributedStringWithFontSize:FONT_SIZE]; - _photoDescriptionLabel.maximumNumberOfLines = 3; - } - - return self; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - switch (_index) { - case 1: - return [self layoutSpecThatFitsNavBar:constrainedSize]; - case 2: - return [self layoutSpecThatFitsASDKgram:constrainedSize]; - default: - return [self layoutSpecThatFitsASDKgram:constrainedSize]; - break; - } -} - -- (ASLayoutSpec *)layoutSpecThatFitsNavBar:(ASSizeRange)constrainedSize -{ - // username / photo location header vertical stack - - _userNameLabel.style.flexShrink = 1.0; - _photoLocationLabel.style.flexShrink = 1.0; - - ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec]; - headerSubStack.style.flexShrink = 1.0; - - if (_photoLocationLabel.attributedText) { - [headerSubStack setChildren:@[_userNameLabel, _photoLocationLabel]]; - } else { - [headerSubStack setChildren:@[_userNameLabel]]; - } - - // header stack - - _userAvatarImageView.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); - _photoTimeIntervalSincePostLabel.style.spacingBefore = HORIZONTAL_BUFFER; // hack to remove double spaces around spacer - - UIEdgeInsets avatarInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER); - ASInsetLayoutSpec *avatarInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:avatarInsets child:_userAvatarImageView]; - - ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; - spacer.style.flexGrow = 1.0; - spacer.style.flexShrink = 1.0; // FIXME: this overrides stuff :) THIS IS A SYSTEMIC ISSUE - can we make layoutSpecThatFits only run once? cache layoutSpec, just use new constrainedSize, don't put properties in layoutSpecThatFits - // separate the idea of laying out and rerunning with new constrainedSize - - ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; - headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack - headerStack.justifyContent = ASStackLayoutJustifyContentStart; // justify content to the left side of the header stack - headerStack.style.flexShrink = 1.0; - headerStack.style.flexGrow = 1.0; - - [headerStack setChildren:@[avatarInset, headerSubStack, spacer, _photoTimeIntervalSincePostLabel]]; - - // header inset stack - - UIEdgeInsets insets = UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER); - ASInsetLayoutSpec *headerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:headerStack]; - headerWithInset.style.flexShrink = 1.0; - headerWithInset.style.flexGrow = 1.0; - - return headerWithInset; -} - -- (ASLayoutSpec *)layoutSpecThatFitsASDKgram:(ASSizeRange)constrainedSize -{ - // username / photo location header vertical stack - - _userNameLabel.style.flexShrink = 1.0; - _photoLocationLabel.style.flexShrink = 1.0; - - ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec]; - headerSubStack.style.flexShrink = 1.0; - - if (_photoLocationLabel.attributedText) { - [headerSubStack setChildren:@[_userNameLabel, _photoLocationLabel]]; - } else { - [headerSubStack setChildren:@[_userNameLabel]]; - } - - // header stack - - _userAvatarImageView.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); - _photoTimeIntervalSincePostLabel.style.spacingBefore = HORIZONTAL_BUFFER; // hack to remove double spaces around spacer - - UIEdgeInsets avatarInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER); - ASInsetLayoutSpec *avatarInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:avatarInsets child:_userAvatarImageView]; - - ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; - spacer.style.flexGrow = 1.0; - spacer.style.flexShrink = 1.0; // FIXME: this overrides stuff :) THIS IS A SYSTEMIC ISSUE - can we make layoutSpecThatFits only run once? cache layoutSpec, just use new constrainedSize, don't put properties in layoutSpecThatFits - // separate the idea of laying out and rerunning with new constrainedSize - - ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; - headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack - headerStack.justifyContent = ASStackLayoutJustifyContentStart; // justify content to the left side of the header stack - headerStack.style.flexShrink = 1.0; - headerStack.style.flexGrow = 1.0; - - [headerStack setChildren:@[avatarInset, headerSubStack, spacer, _photoTimeIntervalSincePostLabel]]; - - // header inset stack - - UIEdgeInsets insets = UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER); - ASInsetLayoutSpec *headerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:headerStack]; - headerWithInset.style.flexShrink = 1.0; - headerWithInset.style.flexGrow = 1.0; - - // footer stack - - ASStackLayoutSpec *footerStack = [ASStackLayoutSpec verticalStackLayoutSpec]; - footerStack.spacing = VERTICAL_BUFFER; - - [footerStack setChildren:@[_photoLikesLabel, _photoDescriptionLabel]]; - - // footer inset stack - - UIEdgeInsets footerInsets = UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER); - ASInsetLayoutSpec *footerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:footerInsets child:footerStack]; - - // vertical stack - - ASRatioLayoutSpec *photoRatioSpec = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1.0 child:_photoImageView]; - - ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec]; - verticalStack.alignItems = ASStackLayoutAlignItemsStretch; // sretch headerStack to fill horizontal space - [verticalStack setChildren:@[headerWithInset, photoRatioSpec, footerWithInset]]; - verticalStack.style.flexShrink = 1.0; - - return verticalStack; -} - -#pragma mark - helper methods - -- (NSAttributedString *)usernameAttributedStringWithFontSize:(CGFloat)size -{ - return [NSAttributedString attributedStringWithString:@"hannahmbanana" - fontSize:size - color:[UIColor darkBlueColor] - firstWordColor:nil]; -} - -- (NSAttributedString *)locationAttributedStringWithFontSize:(CGFloat)size -{ - return [NSAttributedString attributedStringWithString:@"San Fransisco, CA" - fontSize:size - color:[UIColor lightBlueColor] - firstWordColor:nil]; -} - -- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size -{ - return [NSAttributedString attributedStringWithString:@"30m" - fontSize:size - color:[UIColor lightGrayColor] - firstWordColor:nil]; -} - -- (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size -{ - return [NSAttributedString attributedStringWithString:@"♥︎ 17 likes" - fontSize:size - color:[UIColor darkBlueColor] - firstWordColor:nil]; -} - -- (NSAttributedString *)descriptionAttributedStringWithFontSize:(CGFloat)size -{ - NSString *string = [NSString stringWithFormat:@"hannahmbanana check out this cool pic from the internet!"]; - NSAttributedString *attrString = [NSAttributedString attributedStringWithString:string - fontSize:size - color:[UIColor darkGrayColor] - firstWordColor:[UIColor darkBlueColor]]; - return attrString; -} - -@end diff --git a/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.h b/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.h deleted file mode 100644 index 61d54d0f7e..0000000000 --- a/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// PlaygroundContainerNode.h -// Sample -// -// Created by Hannah Troisi on 3/19/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import - -@protocol PlaygroundContainerNodeDelegate - -- (void)relayoutWithSize:(ASSizeRange)size; - -@end - -@interface PlaygroundContainerNode : ASCellNode - -@property (nonatomic, weak) id delegate; - -+ (NSUInteger)containerNodeCount; -- (instancetype)initWithIndex:(NSUInteger)index; - -@end diff --git a/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.m b/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.m deleted file mode 100644 index 7450d471cc..0000000000 --- a/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.m +++ /dev/null @@ -1,124 +0,0 @@ -// -// PlaygroundContainerNode.m -// Sample -// -// Created by Hannah Troisi on 3/19/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import "PlaygroundContainerNode.h" -#import "LayoutExampleNodes.h" -#import "PhotoPostNode.h" -#import -#import - -#define RESIZE_HANDLE_SIZE 30 - -@implementation PlaygroundContainerNode -{ - ASDisplayNode *_playgroundNode; - ASImageNode *_resizeHandle; - CGPoint _resizeStartLocation; -} - -#pragma mark - Lifecycle - -+ (NSUInteger)containerNodeCount -{ - return 5; -} - -+ (ASDisplayNode *)nodeForIndex:(NSUInteger)index -{ - switch (index) { - case 0: return [[HorizontalStackWithSpacer alloc] init]; - case 1: return [[PhotoWithInsetTextOverlay alloc] init]; - case 2: return [[PhotoWithOutsetIconOverlay alloc] init]; - case 3: return [[FlexibleSeparatorSurroundingContent alloc] init]; - case 4: return [[PhotoPostNode alloc] initWithIndex:0]; - default: return [[PhotoPostNode alloc] initWithIndex:1]; - } -} - -- (instancetype)initWithIndex:(NSUInteger)index -{ - self = [super init]; - - if (self) { - self.backgroundColor = [UIColor whiteColor]; //[UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; - self.automaticallyManagesSubnodes = YES; - - _playgroundNode = [[self class] nodeForIndex:index]; - - _resizeHandle = [[ASImageNode alloc] init]; - _resizeHandle.image = [UIImage imageNamed:@"resizeHandle"]; - _resizeHandle.userInteractionEnabled = YES; -// [self addSubnode:_resizeHandle]; - - [ASLayoutElementInspectorNode sharedInstance].style.flexBasis = ASDimensionMakeWithFraction(1.0); - [ASLayoutElementInspectorNode sharedInstance].vizNodeInsetSize = 10.0; - - self.shouldVisualizeLayoutSpecs = NO; - self.shouldCacheLayoutSpec = NO; - } - - return self; -} - -- (void)didLoad -{ - [super didLoad]; - UIPanGestureRecognizer *gr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(resizePlayground:)]; - [_resizeHandle.view addGestureRecognizer:gr]; -} - -// manually layout _resizeHandle // FIXME: add this to an overlayStack in layoutSpecThatFits? -- (void)layout -{ - [super layout]; - [self.view bringSubviewToFront:_resizeHandle.view]; - - CGSize playgroundSize = _playgroundNode.calculatedLayout.size; - CGRect rect = CGRectZero; - rect.size = CGSizeMake(RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE); - rect.origin = CGPointMake(playgroundSize.width - rect.size.width, playgroundSize.height - rect.size.height); - _resizeHandle.frame = rect; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - _playgroundNode.style.flexGrow = 1.0; - _playgroundNode.style.flexShrink = 1.0; - - return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) - child:_playgroundNode]; -} - -#pragma mark - Gesture Handling - -- (void)resizePlayground:(UIPanGestureRecognizer *)sender -{ - if (sender.state == UIGestureRecognizerStateBegan) { - _resizeStartLocation = [sender locationInView:sender.view]; - } - else if (sender.state == UIGestureRecognizerStateChanged) { - CGPoint location = [sender locationInView:sender.view]; - CGPoint translation = CGPointMake(location.x - _resizeStartLocation.x, location.y - _resizeStartLocation.y); - [self changePlaygroundFrameWithTranslation:translation]; - } - else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled || sender.state == UIGestureRecognizerStateFailed) { - _resizeStartLocation = CGPointZero; - } -} - -- (void)changePlaygroundFrameWithTranslation:(CGPoint)translation -{ - ASSizeRange constrainedSize = self.constrainedSizeForCalculatedLayout; - - constrainedSize.max.width = MAX(0, constrainedSize.max.width + translation.x); - constrainedSize.max.height = MAX(0, constrainedSize.max.height + translation.y); - - [self.delegate relayoutWithSize:constrainedSize]; -} - -@end diff --git a/examples/LayoutSpecPlayground/Sample/Utilities.h b/examples/LayoutSpecPlayground/Sample/Utilities.h deleted file mode 100644 index 7f0396e8a8..0000000000 --- a/examples/LayoutSpecPlayground/Sample/Utilities.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// Utilities.h -// Flickrgram -// -// Created by Hannah Troisi on 3/9/16. -// Copyright © 2016 Hannah Troisi. All rights reserved. -// - -#import -#import - -@interface UIColor (Additions) - -+ (UIColor *)darkBlueColor; -+ (UIColor *)lightBlueColor; -+ (UIColor *)duskColor; -+ (UIColor *)customOrangeColor; - -@end - -@interface UIImage (Additions) - -- (UIImage *)makeCircularImageWithSize:(CGSize)size withBorderWidth:(CGFloat)width; - -@end - -@interface NSAttributedString (Additions) - -+ (NSAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size - color:(UIColor *)color firstWordColor:(UIColor *)firstWordColor; - -@end \ No newline at end of file diff --git a/examples/LayoutSpecPlayground/Sample/Utilities.m b/examples/LayoutSpecPlayground/Sample/Utilities.m deleted file mode 100644 index 9b13152a97..0000000000 --- a/examples/LayoutSpecPlayground/Sample/Utilities.m +++ /dev/null @@ -1,98 +0,0 @@ -// -// Utilities.m -// Flickrgram -// -// Created by Hannah Troisi on 3/9/16. -// Copyright © 2016 Hannah Troisi. All rights reserved. -// - -#import "Utilities.h" -#import - -#define StrokeRoundedImages 0 - -@implementation UIColor (Additions) - -+ (UIColor *)darkBlueColor -{ - return [UIColor colorWithRed:18.0/255.0 green:86.0/255.0 blue:136.0/255.0 alpha:1.0]; -} - -+ (UIColor *)lightBlueColor -{ - return [UIColor colorWithRed:0.0 green:122.0/255.0 blue:1.0 alpha:1.0]; -} - -+ (UIColor *)duskColor -{ - return [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; -} - -+ (UIColor *)customOrangeColor -{ - return [UIColor colorWithRed:40/255.0 green:43/255.0 blue:53/255.0 alpha:1.0]; -} - - -@end - -@implementation UIImage (Additions) - -- (UIImage *)makeCircularImageWithSize:(CGSize)size withBorderWidth:(CGFloat)width -{ - // make a CGRect with the image's size - CGRect circleRect = (CGRect) {CGPointZero, size}; - - // begin the image context since we're not in a drawRect: - UIGraphicsBeginImageContextWithOptions(circleRect.size, NO, 0); - - // create a UIBezierPath circle - UIBezierPath *circle = [UIBezierPath bezierPathWithRoundedRect:circleRect cornerRadius:circleRect.size.width/2]; - - // clip to the circle - [circle addClip]; - - [[UIColor whiteColor] set]; - [circle fill]; - - // draw the image in the circleRect *AFTER* the context is clipped - [self drawInRect:circleRect]; - - // create a border (for white background pictures) - if (width > 0) { - circle.lineWidth = width; - [[UIColor whiteColor] set]; - [circle stroke]; - } - - // get an image from the image context - UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext(); - - // end the image context since we're not in a drawRect: - UIGraphicsEndImageContext(); - - return roundedImage; -} - -@end - -@implementation NSAttributedString (Additions) - -+ (NSAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size - color:(nullable UIColor *)color firstWordColor:(nullable UIColor *)firstWordColor -{ - NSDictionary *attributes = @{NSForegroundColorAttributeName: color ? : [UIColor blackColor], - NSFontAttributeName: [UIFont boldSystemFontOfSize:size]}; - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string]; - [attributedString addAttributes:attributes range:NSMakeRange(0, string.length)]; - - if (firstWordColor) { - NSRange firstSpaceRange = [string rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; - NSRange firstWordRange = NSMakeRange(0, firstSpaceRange.location); - [attributedString addAttribute:NSForegroundColorAttributeName value:firstWordColor range:firstWordRange]; - } - - return attributedString; -} - -@end diff --git a/examples/LayoutSpecPlayground/Sample/ViewController.h b/examples/LayoutSpecPlayground/Sample/ViewController.h deleted file mode 100644 index 6e8c8d8ac1..0000000000 --- a/examples/LayoutSpecPlayground/Sample/ViewController.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// ViewController.h -// ASLayoutSpecPlayground -// -// Created by Hannah Troisi on 3/11/16. -// Copyright © 2016 Hannah Troisi. All rights reserved. -// - -#import "AsyncDisplayKit.h" - -@interface ViewController : ASViewController - -@end - diff --git a/examples/LayoutSpecPlayground/Sample/ViewController.m b/examples/LayoutSpecPlayground/Sample/ViewController.m deleted file mode 100644 index 1fe3c96084..0000000000 --- a/examples/LayoutSpecPlayground/Sample/ViewController.m +++ /dev/null @@ -1,92 +0,0 @@ -// -// ViewController.m -// ASLayoutSpecPlayground -// -// Created by Hannah Troisi on 3/11/16. -// Copyright © 2016 Hannah Troisi. All rights reserved. -// - -#import "ViewController.h" -#import "PlaygroundContainerNode.h" -#import "ASLayoutElementInspectorNode.h" - -@interface ViewController () -@end - -@implementation ViewController -{ - ASPagerNode *_pagerNode; - ASSizeRange _sizeRange; -} - -#pragma mark - Lifecycle - -- (instancetype)init -{ - _pagerNode = [[ASPagerNode alloc] init]; - self = [super initWithNode:_pagerNode]; - - if (self) { - _pagerNode.delegate = self; - _pagerNode.dataSource = self; - self.navigationItem.title = @"ASLayoutSpec Playground"; - self.edgesForExtendedLayout = UIRectEdgeNone; - [ASLayoutElementInspectorNode sharedInstance].delegate = self; - } - - return self; -} - -#pragma mark - ASPagerNodeDataSource - -- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode -{ - return [PlaygroundContainerNode containerNodeCount]; -} - -- (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index -{ - return ^{ - PlaygroundContainerNode *containerCellNode = [[PlaygroundContainerNode alloc] initWithIndex:index]; - containerCellNode.delegate = self; - return containerCellNode; - }; -} - -// [ASViewController] Override this method to provide a custom size range to the backing node. -// Neccessary to allow the user to stretch / shrink the size of playground container. -- (ASSizeRange)nodeConstrainedSize -{ - if (CGSizeEqualToSize(_sizeRange.max, CGSizeZero)) { - return [super nodeConstrainedSize]; - } - return _sizeRange; -} - -- (ASSizeRange)pagerNode:(ASPagerNode *)pagerNode constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - if (CGSizeEqualToSize(_sizeRange.max, CGSizeZero)) { - return [super nodeConstrainedSize]; - } - return _sizeRange; -} - -#pragma mark - PlaygroundContainerNodeDelegate - -- (void)relayoutWithSize:(ASSizeRange)size -{ -// NSLog(@"DELEGATE constrainedSize = %@", NSStringFromCGSize(size.max)); - _sizeRange = size; - [self.view setNeedsLayout]; - [_pagerNode reloadData]; -} - -#pragma mark - ASLayoutElementInspectorNodeDelegate - -- (void)toggleVisualization:(BOOL)toggle -{ - NSLog(@"shouldVisualizeLayoutSpecs:%d", toggle); - [self.node setShouldVisualizeLayoutSpecs:toggle]; -} - -@end diff --git a/examples/LayoutSpecPlayground/Sample/main.m b/examples/LayoutSpecPlayground/Sample/main.m deleted file mode 100644 index ae9488711c..0000000000 --- a/examples/LayoutSpecPlayground/Sample/main.m +++ /dev/null @@ -1,20 +0,0 @@ -/* This file provided by Facebook is for non-commercial testing and evaluation - * purposes only. Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/examples/LayoutSpecPlayground/Sample/resizeHandle.png b/examples/LayoutSpecPlayground/Sample/resizeHandle.png deleted file mode 100644 index 86404bcb72..0000000000 Binary files a/examples/LayoutSpecPlayground/Sample/resizeHandle.png and /dev/null differ diff --git a/examples/PagerNode/Podfile b/examples/PagerNode/Podfile index 919de4b311..defaf55058 100644 --- a/examples/PagerNode/Podfile +++ b/examples/PagerNode/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/PagerNode/Sample.xcodeproj/project.pbxproj b/examples/PagerNode/Sample.xcodeproj/project.pbxproj index ea21795fa1..16d7ec8406 100644 --- a/examples/PagerNode/Sample.xcodeproj/project.pbxproj +++ b/examples/PagerNode/Sample.xcodeproj/project.pbxproj @@ -1,776 +1,372 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 0585427F19D4DBE100606EA6 - - isa - PBXFileReference - lastKnownFileType - image.png - name - Default-568h@2x.png - path - ../Default-568h@2x.png - sourceTree - <group> - - 0585428019D4DBE100606EA6 - - fileRef - 0585427F19D4DBE100606EA6 - isa - PBXBuildFile - - 05E2127819D4DB510098F589 - - children - - 05E2128319D4DB510098F589 - 05E2128219D4DB510098F589 - 1A943BF0259746F18D6E423F - 1AE410B73DA5C3BD087ACDD7 - - indentWidth - 2 - isa - PBXGroup - sourceTree - <group> - tabWidth - 2 - usesTabs - 0 - - 05E2127919D4DB510098F589 - - attributes - - LastUpgradeCheck - 0710 - ORGANIZATIONNAME - Facebook - TargetAttributes - - 05E2128019D4DB510098F589 - - CreatedOnToolsVersion - 6.0.1 - - - - buildConfigurationList - 05E2127C19D4DB510098F589 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - 05E2127819D4DB510098F589 - productRefGroup - 05E2128219D4DB510098F589 - projectDirPath - - projectReferences - - projectRoot - - targets - - 05E2128019D4DB510098F589 - - - 05E2127C19D4DB510098F589 - - buildConfigurations - - 05E212A219D4DB510098F589 - 05E212A319D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E2127D19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 05E2128D19D4DB510098F589 - 05E2128A19D4DB510098F589 - 05E2128719D4DB510098F589 - 252041E21C167DFC00E264C8 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127E19D4DB510098F589 - - buildActionMask - 2147483647 - files - - DFE855DDBC731242D3515B58 - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127F19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 0585428019D4DBE100606EA6 - 6C2C82AC19EE274300767484 - 6C2C82AD19EE274300767484 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2128019D4DB510098F589 - - buildConfigurationList - 05E212A419D4DB510098F589 - buildPhases - - E080B80F89C34A25B3488E26 - 05E2127D19D4DB510098F589 - 05E2127E19D4DB510098F589 - 05E2127F19D4DB510098F589 - F012A6F39E0149F18F564F50 - 6E05308BEF86AD80AEB4EEE7 - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - 05E2128119D4DB510098F589 - productType - com.apple.product-type.application - - 05E2128119D4DB510098F589 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - 05E2128219D4DB510098F589 - - children - - 05E2128119D4DB510098F589 - - isa - PBXGroup - name - Products - sourceTree - <group> - - 05E2128319D4DB510098F589 - - children - - 252041E01C167DFC00E264C8 - 252041E11C167DFC00E264C8 - 05E2128819D4DB510098F589 - 05E2128919D4DB510098F589 - 05E2128B19D4DB510098F589 - 05E2128C19D4DB510098F589 - 05E2128419D4DB510098F589 - - isa - PBXGroup - path - Sample - sourceTree - <group> - - 05E2128419D4DB510098F589 - - children - - 0585427F19D4DBE100606EA6 - 6C2C82AA19EE274300767484 - 6C2C82AB19EE274300767484 - 05E2128519D4DB510098F589 - 05E2128619D4DB510098F589 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - 05E2128519D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - 05E2128619D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - 05E2128719D4DB510098F589 - - fileRef - 05E2128619D4DB510098F589 - isa - PBXBuildFile - - 05E2128819D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - 05E2128919D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - 05E2128A19D4DB510098F589 - - fileRef - 05E2128919D4DB510098F589 - isa - PBXBuildFile - - 05E2128B19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - 05E2128C19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - 05E2128D19D4DB510098F589 - - fileRef - 05E2128C19D4DB510098F589 - isa - PBXBuildFile - - 05E212A219D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - ENABLE_TESTABILITY - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_SYMBOLS_PRIVATE_EXTERN - NO - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - - isa - XCBuildConfiguration - name - Debug - - 05E212A319D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - YES - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - 05E212A419D4DB510098F589 - - buildConfigurations - - 05E212A519D4DB510098F589 - 05E212A619D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E212A519D4DB510098F589 - - baseConfigurationReference - 79ED4D85CC60068C341CFD77 - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_BUNDLE_IDENTIFIER - com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Debug - - 05E212A619D4DB510098F589 - - baseConfigurationReference - 1C47DEC3F9D2BD9AD5F5CD67 - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_BUNDLE_IDENTIFIER - com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Release - - 1A943BF0259746F18D6E423F - - children - - C284F7E957985CA251284B05 - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - 1AE410B73DA5C3BD087ACDD7 - - children - - 79ED4D85CC60068C341CFD77 - 1C47DEC3F9D2BD9AD5F5CD67 - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 1C47DEC3F9D2BD9AD5F5CD67 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - 252041E01C167DFC00E264C8 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - PageNode.h - sourceTree - <group> - - 252041E11C167DFC00E264C8 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - PageNode.m - sourceTree - <group> - - 252041E21C167DFC00E264C8 - - fileRef - 252041E11C167DFC00E264C8 - isa - PBXBuildFile - - 6C2C82AA19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-667h@2x.png - sourceTree - SOURCE_ROOT - - 6C2C82AB19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-736h@3x.png - sourceTree - SOURCE_ROOT - - 6C2C82AC19EE274300767484 - - fileRef - 6C2C82AA19EE274300767484 - isa - PBXBuildFile - - 6C2C82AD19EE274300767484 - - fileRef - 6C2C82AB19EE274300767484 - isa - PBXBuildFile - - 6E05308BEF86AD80AEB4EEE7 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - 79ED4D85CC60068C341CFD77 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - C284F7E957985CA251284B05 - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - DFE855DDBC731242D3515B58 - - fileRef - C284F7E957985CA251284B05 - isa - PBXBuildFile - - E080B80F89C34A25B3488E26 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - F012A6F39E0149F18F564F50 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - - rootObject - 05E2127919D4DB510098F589 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 252041E21C167DFC00E264C8 /* PageNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 252041E11C167DFC00E264C8 /* PageNode.m */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; + DFE855DDBC731242D3515B58 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C284F7E957985CA251284B05 /* libPods-Sample.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + 252041E01C167DFC00E264C8 /* PageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PageNode.h; sourceTree = ""; }; + 252041E11C167DFC00E264C8 /* PageNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PageNode.m; sourceTree = ""; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + 79ED4D85CC60068C341CFD77 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + C284F7E957985CA251284B05 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DFE855DDBC731242D3515B58 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 252041E01C167DFC00E264C8 /* PageNode.h */, + 252041E11C167DFC00E264C8 /* PageNode.m */, + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + C284F7E957985CA251284B05 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + 79ED4D85CC60068C341CFD77 /* Pods-Sample.debug.xcconfig */, + 1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + 6E05308BEF86AD80AEB4EEE7 /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0710; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 6E05308BEF86AD80AEB4EEE7 /* 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-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + 252041E21C167DFC00E264C8 /* PageNode.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 79ED4D85CC60068C341CFD77 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples/PagerNode/Sample/PageNode.m b/examples/PagerNode/Sample/PageNode.m index 75f9942e7d..bda108a1bf 100644 --- a/examples/PagerNode/Sample/PageNode.m +++ b/examples/PagerNode/Sample/PageNode.m @@ -21,9 +21,9 @@ @implementation PageNode -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize +- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - return [ASLayout layoutWithLayoutElement:self size:constrainedSize.max]; + return constrainedSize; } - (void)fetchData diff --git a/examples/PagerNode/Sample/ViewController.m b/examples/PagerNode/Sample/ViewController.m index 8adf45354c..001c5c069a 100644 --- a/examples/PagerNode/Sample/ViewController.m +++ b/examples/PagerNode/Sample/ViewController.m @@ -46,7 +46,7 @@ - (instancetype)init self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Next" style:UIBarButtonItemStylePlain target:self action:@selector(scrollToNextPage:)]; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Previous" style:UIBarButtonItemStylePlain target:self action:@selector(scrollToPreviousPage:)]; - + self.automaticallyAdjustsScrollViewInsets = NO; return self; } diff --git a/examples/README.md b/examples/README.md index 87c32c6375..e56667c0ce 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,20 +9,36 @@ dependencies. ### ASCollectionView [ObjC] -![ASCollectionView Example App Screenshot](./Screenshots/ASCollectionView.png?raw=true) +![ASCollectionView Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/ASCollectionView.png) Featuring: - ASCollectionView with header/footer supplementary node support - ASCollectionView batch API - ASDelegateProxy +### ASDKgram [ObjC] + +![ASDKgram Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/ASDKgram.png) + +### ASDKLayoutTransition [ObjC] + +![ASDKLayoutTransition Example App](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/ASDKLayoutTransition.gif) + +### ASDKTube [ObjC] + +![ASDKTube Example App](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/ASDKTube.gif) + +### ASMapNode [ObjC] + +![ASMapNode Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/ASMapNode.png) + ### ASTableViewStressTest [ObjC] -![ASTableViewStressTest Example App Screenshot](./Screenshots/ASTableViewStressTest.png?raw=true) +![ASTableViewStressTest Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/ASTableViewStressTest.png) ### ASViewController [ObjC] -![ASViewController Example App Screenshot](./Screenshots/ASViewController.png?raw=true) +![ASViewController Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/ASViewController.png) Featuring: - ASViewController @@ -30,9 +46,13 @@ Featuring: - ASMultiplexImageNode - ASLayoutSpec +### AsyncDisplayKitOverview [ObjC] + +![AsyncDisplayKitOverview Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/AsyncDisplayKitOverview.png) + ### BackgroundPropertySetting [Swift] -![BackgroundPropertySetting Example App gif](./Screenshots/BackgroundPropertySetting.gif?raw=true) +![BackgroundPropertySetting Example App gif](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/BackgroundPropertySetting.gif) Featuring: - ASDK Swift compatibility @@ -44,7 +64,7 @@ Featuring: ### CarthageBuildTest ### CatDealsCollectionView [ObjC] -![CatDealsCollectionView Example App Screenshot](./Screenshots/CatDealsCollectionView.png?raw=true) +![CatDealsCollectionView Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/CatDealsCollectionView.png) Featuring: - ASCollectionView @@ -54,16 +74,16 @@ Featuring: ### CollectionViewWithViewControllerCells [ObjC] -![CollectionViewWithViewControllerCells Example App Screenshot](./Screenshots/CollectionViewWithViewControllerCells.png?raw=true) +![CollectionViewWithViewControllerCells Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/CollectionViewWithViewControllerCells.png) Featuring: - custom collection view layout - ASLayoutSpec - ASMultiplexImageNode -### CustomCollectionView [ObjC] +### CustomCollectionView [ObjC+Swift] -![CustomCollectionView Example App gif](./Screenshots/CustomCollectionView.gif?raw=true) +![CustomCollectionView Example App gif](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/CustomCollectionView.git) Featuring: - custom collection view layout @@ -71,14 +91,14 @@ Featuring: ### EditableText [ObjC] -![EditableText Example App Screenshot](./Screenshots/EditableText.png?raw=true) +![EditableText Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/EditableText.png) Featuring: - ASEditableTextNode ### HorizontalwithinVerticalScrolling [ObjC] -![HorizontalwithinVerticalScrolling Example App gif](./Screenshots/HorizontalwithinVerticalScrolling.gif?raw=true) +![HorizontalwithinVerticalScrolling Example App gif](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/HorizontalwithinVerticalScrolling.gif) Featuring: - UIViewController with ASTableView @@ -87,15 +107,19 @@ Featuring: ### Kittens [ObjC] -![Kittens Example App Screenshot](./Screenshots/Kittens.png?raw=true) +![Kittens Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/Kittens.png) Featuring: - UIViewController with ASTableView - ASCellNodes with ASNetworkImageNode and ASTextNode +### LayoutSpecPlayground [ObjC] + +![LayoutSpecPlayground Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/LayoutSpecPlayground.png) + ### Multiplex [ObjC] -![Multiplex Example App gif](./Screenshots/Multiplex.gif?raw=true) +![Multiplex Example App](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/Multiplex.gif) Featuring: - ASMultiplexImageNode (with artificial delay inserted) @@ -103,6 +127,8 @@ Featuring: ### PagerNode [ObjC] +![PagerNode Example App](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/PagerNode.gif) + Featuring: - ASPagerNode @@ -113,7 +139,7 @@ Featuring: ### SocialAppLayout [ObjC] -![SocialAppLayout Example App Screenshot](./Screenshots/SocialAppLayout.png?raw=true) +![SocialAppLayout Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/SocialAppLayout.png) Featuring: - ASLayoutSpec @@ -121,14 +147,14 @@ Featuring: ### Swift [Swift] -![Swift Example App Screenshot](./Screenshots/Swift.png?raw=true) +![Swift Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/Swift.png) Featuring: - ASViewController with ASTableNode ### SynchronousConcurrency [ObjC] -![SynchronousConcurrency Example App Screenshot](./Screenshots/SynchronousConcurrency.png?raw=true) +![SynchronousConcurrency Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/SynchronousConcurrency.png) Implementation of Synchronous Concurrency features for AsyncDisplayKit 2.0 @@ -156,21 +182,21 @@ cell types may be appropriate to display to the user with placeholders, whereas ### VerticalWithinHorizontalScrolling [ObjC] -![VerticalWithinHorizontalScrolling Example App Screenshot](./Screenshots/VerticalWithinHorizontalScrolling.png?raw=true) +![VerticalWithinHorizontalScrolling Example App](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/VerticalWithinHorizontalScrolling.gif) Features: - UIViewController containing ASPagerNode containing ASTableNodes ### Videos [ObjC] -![VideoTableView Example App gif](./Screenshots/Videos.gif?raw=true) +![VideoTableView Example App gif](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/Videos.gif) Featuring: - ASVideoNode ### VideoTableView [ObjC] -![VideoTableView Example App Screenshot](./Screenshots/VideoTableView.png?raw=true) +![VideoTableView Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/VideoTableView.png) Featuring: - ASVideoNode diff --git a/examples/Screenshots/ASCollectionView.png b/examples/Screenshots/ASCollectionView.png deleted file mode 100644 index 3aaff368e5..0000000000 Binary files a/examples/Screenshots/ASCollectionView.png and /dev/null differ diff --git a/examples/Screenshots/ASTableViewStressTest.png b/examples/Screenshots/ASTableViewStressTest.png deleted file mode 100644 index cd7aae2d9d..0000000000 Binary files a/examples/Screenshots/ASTableViewStressTest.png and /dev/null differ diff --git a/examples/Screenshots/ASViewController.png b/examples/Screenshots/ASViewController.png deleted file mode 100644 index 545f3c8d63..0000000000 Binary files a/examples/Screenshots/ASViewController.png and /dev/null differ diff --git a/examples/Screenshots/BackgroundPropertySetting.gif b/examples/Screenshots/BackgroundPropertySetting.gif deleted file mode 100644 index e2655ef235..0000000000 Binary files a/examples/Screenshots/BackgroundPropertySetting.gif and /dev/null differ diff --git a/examples/Screenshots/CatDealsCollectionView.png b/examples/Screenshots/CatDealsCollectionView.png deleted file mode 100644 index 72f9179e94..0000000000 Binary files a/examples/Screenshots/CatDealsCollectionView.png and /dev/null differ diff --git a/examples/Screenshots/CollectionViewWithViewControllerCells.png b/examples/Screenshots/CollectionViewWithViewControllerCells.png deleted file mode 100644 index 078b7b945f..0000000000 Binary files a/examples/Screenshots/CollectionViewWithViewControllerCells.png and /dev/null differ diff --git a/examples/Screenshots/CustomCollectionView.gif b/examples/Screenshots/CustomCollectionView.gif deleted file mode 100644 index 8d8a2aed1e..0000000000 Binary files a/examples/Screenshots/CustomCollectionView.gif and /dev/null differ diff --git a/examples/Screenshots/EditableText.png b/examples/Screenshots/EditableText.png deleted file mode 100644 index 1aa8d6db9b..0000000000 Binary files a/examples/Screenshots/EditableText.png and /dev/null differ diff --git a/examples/Screenshots/HorizontalwithinVerticalScrolling.gif b/examples/Screenshots/HorizontalwithinVerticalScrolling.gif deleted file mode 100644 index fe722a308d..0000000000 Binary files a/examples/Screenshots/HorizontalwithinVerticalScrolling.gif and /dev/null differ diff --git a/examples/Screenshots/Kittens.png b/examples/Screenshots/Kittens.png deleted file mode 100644 index 91d9bf36fa..0000000000 Binary files a/examples/Screenshots/Kittens.png and /dev/null differ diff --git a/examples/Screenshots/Multiplex.gif b/examples/Screenshots/Multiplex.gif deleted file mode 100644 index fcb98cad62..0000000000 Binary files a/examples/Screenshots/Multiplex.gif and /dev/null differ diff --git a/examples/Screenshots/SocialAppLayout.png b/examples/Screenshots/SocialAppLayout.png deleted file mode 100644 index 33d6672102..0000000000 Binary files a/examples/Screenshots/SocialAppLayout.png and /dev/null differ diff --git a/examples/Screenshots/Swift.png b/examples/Screenshots/Swift.png deleted file mode 100644 index b099a17229..0000000000 Binary files a/examples/Screenshots/Swift.png and /dev/null differ diff --git a/examples/Screenshots/SynchronousConcurrency.png b/examples/Screenshots/SynchronousConcurrency.png deleted file mode 100644 index e0eac3788e..0000000000 Binary files a/examples/Screenshots/SynchronousConcurrency.png and /dev/null differ diff --git a/examples/Screenshots/VerticalWithinHorizontalScrolling.gif b/examples/Screenshots/VerticalWithinHorizontalScrolling.gif deleted file mode 100644 index c50474b19b..0000000000 Binary files a/examples/Screenshots/VerticalWithinHorizontalScrolling.gif and /dev/null differ diff --git a/examples/Screenshots/VideoTableView.png b/examples/Screenshots/VideoTableView.png deleted file mode 100644 index ddeefdc773..0000000000 Binary files a/examples/Screenshots/VideoTableView.png and /dev/null differ diff --git a/examples/Screenshots/Videos.gif b/examples/Screenshots/Videos.gif deleted file mode 100644 index 5e5c5f2ca9..0000000000 Binary files a/examples/Screenshots/Videos.gif and /dev/null differ diff --git a/examples/LayoutSpecPlayground/Podfile b/examples/SocialAppLayout-Inverted/Podfile similarity index 83% rename from examples/LayoutSpecPlayground/Podfile rename to examples/SocialAppLayout-Inverted/Podfile index 724da8c9e4..defaf55058 100644 --- a/examples/LayoutSpecPlayground/Podfile +++ b/examples/SocialAppLayout-Inverted/Podfile @@ -1,6 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.1' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end - diff --git a/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.pbxproj b/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..b43fcaac6e --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,482 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 3EEA4EE91BECC4A1008A7F35 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4EE81BECC4A1008A7F35 /* main.m */; }; + 3EEA4EEC1BECC4A1008A7F35 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4EEB1BECC4A1008A7F35 /* AppDelegate.m */; }; + 3EEA4EEF1BECC4A1008A7F35 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4EEE1BECC4A1008A7F35 /* ViewController.m */; }; + 3EEA4EF41BECC4A1008A7F35 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4EF31BECC4A1008A7F35 /* Assets.xcassets */; }; + 3EEA4F011BECC4E8008A7F35 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4EFE1BECC4E8008A7F35 /* Default-568h@2x.png */; }; + 3EEA4F021BECC4E8008A7F35 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4EFF1BECC4E8008A7F35 /* Default-667h@2x.png */; }; + 3EEA4F031BECC4E8008A7F35 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F001BECC4E8008A7F35 /* Default-736h@3x.png */; }; + 3EEA4F061BECC6C9008A7F35 /* Post.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F051BECC6C9008A7F35 /* Post.m */; }; + 3EEA4F091BECC855008A7F35 /* PostNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F081BECC855008A7F35 /* PostNode.m */; }; + 3EEA4F0C1BECCA0A008A7F35 /* TextStyles.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F0B1BECCA0A008A7F35 /* TextStyles.m */; }; + 3EEA4F141BECDCD6008A7F35 /* icon_android.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F0E1BECDCD6008A7F35 /* icon_android.png */; }; + 3EEA4F151BECDCD6008A7F35 /* icon_android@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F0F1BECDCD6008A7F35 /* icon_android@2x.png */; }; + 3EEA4F161BECDCD6008A7F35 /* icon_android@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F101BECDCD6008A7F35 /* icon_android@3x.png */; }; + 3EEA4F171BECDCD6008A7F35 /* icon_ios.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F111BECDCD6008A7F35 /* icon_ios.png */; }; + 3EEA4F181BECDCD6008A7F35 /* icon_ios@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F121BECDCD6008A7F35 /* icon_ios@2x.png */; }; + 3EEA4F191BECDCD6008A7F35 /* icon_ios@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F131BECDCD6008A7F35 /* icon_ios@3x.png */; }; + 3EEA4F1D1BECE358008A7F35 /* LikesNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F1C1BECE358008A7F35 /* LikesNode.m */; }; + 3EEA4F2A1BECE440008A7F35 /* icon_liked.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F1E1BECE440008A7F35 /* icon_liked.png */; }; + 3EEA4F2B1BECE440008A7F35 /* icon_liked@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F1F1BECE440008A7F35 /* icon_liked@2x.png */; }; + 3EEA4F2C1BECE440008A7F35 /* icon_liked@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F201BECE440008A7F35 /* icon_liked@3x.png */; }; + 3EEA4F301BECE440008A7F35 /* icon_like@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F241BECE440008A7F35 /* icon_like@3x.png */; }; + 3EEA4F311BECE440008A7F35 /* icon_like@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F251BECE440008A7F35 /* icon_like@2x.png */; }; + 3EEA4F321BECE440008A7F35 /* icon_like.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F261BECE440008A7F35 /* icon_like.png */; }; + 3EEA4F331BECE440008A7F35 /* icon_comment@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F271BECE440008A7F35 /* icon_comment@3x.png */; }; + 3EEA4F341BECE440008A7F35 /* icon_comment@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F281BECE440008A7F35 /* icon_comment@2x.png */; }; + 3EEA4F351BECE440008A7F35 /* icon_comment.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F291BECE440008A7F35 /* icon_comment.png */; }; + 3EEA4F381BECE775008A7F35 /* CommentsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F371BECE775008A7F35 /* CommentsNode.m */; }; + 3EEA4F3C1BECE99F008A7F35 /* icon_more.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F391BECE99F008A7F35 /* icon_more.png */; }; + 3EEA4F3D1BECE99F008A7F35 /* icon_more@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F3A1BECE99F008A7F35 /* icon_more@2x.png */; }; + 3EEA4F3E1BECE99F008A7F35 /* icon_more@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F3B1BECE99F008A7F35 /* icon_more@3x.png */; }; + 93964C9FCC28D92625106430 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 193CE60FE6429EFEBF6EA52B /* libPods-Sample.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 193CE60FE6429EFEBF6EA52B /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3EEA4EE41BECC4A1008A7F35 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 3EEA4EE81BECC4A1008A7F35 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 3EEA4EEA1BECC4A1008A7F35 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 3EEA4EEB1BECC4A1008A7F35 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 3EEA4EED1BECC4A1008A7F35 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 3EEA4EEE1BECC4A1008A7F35 /* ViewController.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; tabWidth = 2; }; + 3EEA4EF31BECC4A1008A7F35 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 3EEA4EF81BECC4A1008A7F35 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3EEA4EFE1BECC4E8008A7F35 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; + 3EEA4EFF1BECC4E8008A7F35 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = ""; }; + 3EEA4F001BECC4E8008A7F35 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = ""; }; + 3EEA4F041BECC6C9008A7F35 /* Post.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Post.h; sourceTree = ""; }; + 3EEA4F051BECC6C9008A7F35 /* Post.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Post.m; sourceTree = ""; }; + 3EEA4F071BECC855008A7F35 /* PostNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PostNode.h; sourceTree = ""; }; + 3EEA4F081BECC855008A7F35 /* PostNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostNode.m; sourceTree = ""; }; + 3EEA4F0A1BECCA0A008A7F35 /* TextStyles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextStyles.h; sourceTree = ""; }; + 3EEA4F0B1BECCA0A008A7F35 /* TextStyles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TextStyles.m; sourceTree = ""; }; + 3EEA4F0E1BECDCD6008A7F35 /* icon_android.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_android.png; sourceTree = ""; }; + 3EEA4F0F1BECDCD6008A7F35 /* icon_android@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_android@2x.png"; sourceTree = ""; }; + 3EEA4F101BECDCD6008A7F35 /* icon_android@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_android@3x.png"; sourceTree = ""; }; + 3EEA4F111BECDCD6008A7F35 /* icon_ios.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_ios.png; sourceTree = ""; }; + 3EEA4F121BECDCD6008A7F35 /* icon_ios@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_ios@2x.png"; sourceTree = ""; }; + 3EEA4F131BECDCD6008A7F35 /* icon_ios@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_ios@3x.png"; sourceTree = ""; }; + 3EEA4F1B1BECE358008A7F35 /* LikesNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LikesNode.h; sourceTree = ""; }; + 3EEA4F1C1BECE358008A7F35 /* LikesNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LikesNode.m; sourceTree = ""; }; + 3EEA4F1E1BECE440008A7F35 /* icon_liked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_liked.png; sourceTree = ""; }; + 3EEA4F1F1BECE440008A7F35 /* icon_liked@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_liked@2x.png"; sourceTree = ""; }; + 3EEA4F201BECE440008A7F35 /* icon_liked@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_liked@3x.png"; sourceTree = ""; }; + 3EEA4F241BECE440008A7F35 /* icon_like@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_like@3x.png"; sourceTree = ""; }; + 3EEA4F251BECE440008A7F35 /* icon_like@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_like@2x.png"; sourceTree = ""; }; + 3EEA4F261BECE440008A7F35 /* icon_like.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_like.png; sourceTree = ""; }; + 3EEA4F271BECE440008A7F35 /* icon_comment@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_comment@3x.png"; sourceTree = ""; }; + 3EEA4F281BECE440008A7F35 /* icon_comment@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_comment@2x.png"; sourceTree = ""; }; + 3EEA4F291BECE440008A7F35 /* icon_comment.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_comment.png; sourceTree = ""; }; + 3EEA4F361BECE775008A7F35 /* CommentsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentsNode.h; sourceTree = ""; }; + 3EEA4F371BECE775008A7F35 /* CommentsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentsNode.m; sourceTree = ""; }; + 3EEA4F391BECE99F008A7F35 /* icon_more.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_more.png; sourceTree = ""; }; + 3EEA4F3A1BECE99F008A7F35 /* icon_more@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_more@2x.png"; sourceTree = ""; }; + 3EEA4F3B1BECE99F008A7F35 /* icon_more@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_more@3x.png"; sourceTree = ""; }; + CC6F2ABE8383FAB21802C734 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + FCCC1AD413FCA8603156ED15 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3EEA4EE11BECC4A1008A7F35 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 93964C9FCC28D92625106430 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3EEA4EDB1BECC4A1008A7F35 = { + isa = PBXGroup; + children = ( + 3EEA4EE61BECC4A1008A7F35 /* Sample */, + 3EEA4EE51BECC4A1008A7F35 /* Products */, + 842ADAFE88475D19B24183AC /* Pods */, + EED34FA6D8171DF44757C852 /* Frameworks */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + }; + 3EEA4EE51BECC4A1008A7F35 /* Products */ = { + isa = PBXGroup; + children = ( + 3EEA4EE41BECC4A1008A7F35 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 3EEA4EE61BECC4A1008A7F35 /* Sample */ = { + isa = PBXGroup; + children = ( + 3EEA4F0D1BECDCA6008A7F35 /* Images */, + 3EEA4EEA1BECC4A1008A7F35 /* AppDelegate.h */, + 3EEA4EEB1BECC4A1008A7F35 /* AppDelegate.m */, + 3EEA4EED1BECC4A1008A7F35 /* ViewController.h */, + 3EEA4EEE1BECC4A1008A7F35 /* ViewController.m */, + 3EEA4EF31BECC4A1008A7F35 /* Assets.xcassets */, + 3EEA4EF81BECC4A1008A7F35 /* Info.plist */, + 3EEA4EE71BECC4A1008A7F35 /* Supporting Files */, + 3EEA4F041BECC6C9008A7F35 /* Post.h */, + 3EEA4F051BECC6C9008A7F35 /* Post.m */, + 3EEA4F071BECC855008A7F35 /* PostNode.h */, + 3EEA4F081BECC855008A7F35 /* PostNode.m */, + 3EEA4F0A1BECCA0A008A7F35 /* TextStyles.h */, + 3EEA4F0B1BECCA0A008A7F35 /* TextStyles.m */, + 3EEA4F1B1BECE358008A7F35 /* LikesNode.h */, + 3EEA4F1C1BECE358008A7F35 /* LikesNode.m */, + 3EEA4F361BECE775008A7F35 /* CommentsNode.h */, + 3EEA4F371BECE775008A7F35 /* CommentsNode.m */, + ); + path = Sample; + sourceTree = ""; + }; + 3EEA4EE71BECC4A1008A7F35 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 3EEA4EFE1BECC4E8008A7F35 /* Default-568h@2x.png */, + 3EEA4EFF1BECC4E8008A7F35 /* Default-667h@2x.png */, + 3EEA4F001BECC4E8008A7F35 /* Default-736h@3x.png */, + 3EEA4EE81BECC4A1008A7F35 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 3EEA4F0D1BECDCA6008A7F35 /* Images */ = { + isa = PBXGroup; + children = ( + 3EEA4F391BECE99F008A7F35 /* icon_more.png */, + 3EEA4F3A1BECE99F008A7F35 /* icon_more@2x.png */, + 3EEA4F3B1BECE99F008A7F35 /* icon_more@3x.png */, + 3EEA4F1E1BECE440008A7F35 /* icon_liked.png */, + 3EEA4F1F1BECE440008A7F35 /* icon_liked@2x.png */, + 3EEA4F201BECE440008A7F35 /* icon_liked@3x.png */, + 3EEA4F241BECE440008A7F35 /* icon_like@3x.png */, + 3EEA4F251BECE440008A7F35 /* icon_like@2x.png */, + 3EEA4F261BECE440008A7F35 /* icon_like.png */, + 3EEA4F271BECE440008A7F35 /* icon_comment@3x.png */, + 3EEA4F281BECE440008A7F35 /* icon_comment@2x.png */, + 3EEA4F291BECE440008A7F35 /* icon_comment.png */, + 3EEA4F0E1BECDCD6008A7F35 /* icon_android.png */, + 3EEA4F0F1BECDCD6008A7F35 /* icon_android@2x.png */, + 3EEA4F101BECDCD6008A7F35 /* icon_android@3x.png */, + 3EEA4F111BECDCD6008A7F35 /* icon_ios.png */, + 3EEA4F121BECDCD6008A7F35 /* icon_ios@2x.png */, + 3EEA4F131BECDCD6008A7F35 /* icon_ios@3x.png */, + ); + name = Images; + sourceTree = ""; + }; + 842ADAFE88475D19B24183AC /* Pods */ = { + isa = PBXGroup; + children = ( + CC6F2ABE8383FAB21802C734 /* Pods-Sample.debug.xcconfig */, + FCCC1AD413FCA8603156ED15 /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + EED34FA6D8171DF44757C852 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 193CE60FE6429EFEBF6EA52B /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 3EEA4EE31BECC4A1008A7F35 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3EEA4EFB1BECC4A1008A7F35 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + B5BD9E5609B2CB179EEE0CF4 /* [CP] Check Pods Manifest.lock */, + 3EEA4EE01BECC4A1008A7F35 /* Sources */, + 3EEA4EE11BECC4A1008A7F35 /* Frameworks */, + 3EEA4EE21BECC4A1008A7F35 /* Resources */, + 21F2C1D9B53F9468EAF1653F /* [CP] Copy Pods Resources */, + 852437589F1D53B9483A75DF /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 3EEA4EE41BECC4A1008A7F35 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 3EEA4EDC1BECC4A1008A7F35 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0710; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 3EEA4EE31BECC4A1008A7F35 = { + CreatedOnToolsVersion = 7.1; + }; + }; + }; + buildConfigurationList = 3EEA4EDF1BECC4A1008A7F35 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 3EEA4EDB1BECC4A1008A7F35; + productRefGroup = 3EEA4EE51BECC4A1008A7F35 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 3EEA4EE31BECC4A1008A7F35 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 3EEA4EE21BECC4A1008A7F35 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3EEA4F2A1BECE440008A7F35 /* icon_liked.png in Resources */, + 3EEA4F181BECDCD6008A7F35 /* icon_ios@2x.png in Resources */, + 3EEA4F311BECE440008A7F35 /* icon_like@2x.png in Resources */, + 3EEA4F3D1BECE99F008A7F35 /* icon_more@2x.png in Resources */, + 3EEA4F141BECDCD6008A7F35 /* icon_android.png in Resources */, + 3EEA4F3E1BECE99F008A7F35 /* icon_more@3x.png in Resources */, + 3EEA4F2B1BECE440008A7F35 /* icon_liked@2x.png in Resources */, + 3EEA4F351BECE440008A7F35 /* icon_comment.png in Resources */, + 3EEA4EF41BECC4A1008A7F35 /* Assets.xcassets in Resources */, + 3EEA4F171BECDCD6008A7F35 /* icon_ios.png in Resources */, + 3EEA4F021BECC4E8008A7F35 /* Default-667h@2x.png in Resources */, + 3EEA4F161BECDCD6008A7F35 /* icon_android@3x.png in Resources */, + 3EEA4F191BECDCD6008A7F35 /* icon_ios@3x.png in Resources */, + 3EEA4F331BECE440008A7F35 /* icon_comment@3x.png in Resources */, + 3EEA4F341BECE440008A7F35 /* icon_comment@2x.png in Resources */, + 3EEA4F321BECE440008A7F35 /* icon_like.png in Resources */, + 3EEA4F151BECDCD6008A7F35 /* icon_android@2x.png in Resources */, + 3EEA4F301BECE440008A7F35 /* icon_like@3x.png in Resources */, + 3EEA4F2C1BECE440008A7F35 /* icon_liked@3x.png in Resources */, + 3EEA4F011BECC4E8008A7F35 /* Default-568h@2x.png in Resources */, + 3EEA4F031BECC4E8008A7F35 /* Default-736h@3x.png in Resources */, + 3EEA4F3C1BECE99F008A7F35 /* icon_more.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 21F2C1D9B53F9468EAF1653F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 852437589F1D53B9483A75DF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B5BD9E5609B2CB179EEE0CF4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3EEA4EE01BECC4A1008A7F35 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3EEA4EEF1BECC4A1008A7F35 /* ViewController.m in Sources */, + 3EEA4EEC1BECC4A1008A7F35 /* AppDelegate.m in Sources */, + 3EEA4EE91BECC4A1008A7F35 /* main.m in Sources */, + 3EEA4F061BECC6C9008A7F35 /* Post.m in Sources */, + 3EEA4F0C1BECCA0A008A7F35 /* TextStyles.m in Sources */, + 3EEA4F381BECE775008A7F35 /* CommentsNode.m in Sources */, + 3EEA4F091BECC855008A7F35 /* PostNode.m in Sources */, + 3EEA4F1D1BECE358008A7F35 /* LikesNode.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 3EEA4EF91BECC4A1008A7F35 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 3EEA4EFA1BECC4A1008A7F35 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 3EEA4EFC1BECC4A1008A7F35 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CC6F2ABE8383FAB21802C734 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3EEA4EFD1BECC4A1008A7F35 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FCCC1AD413FCA8603156ED15 /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3EEA4EDF1BECC4A1008A7F35 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3EEA4EF91BECC4A1008A7F35 /* Debug */, + 3EEA4EFA1BECC4A1008A7F35 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3EEA4EFB1BECC4A1008A7F35 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3EEA4EFC1BECC4A1008A7F35 /* Debug */, + 3EEA4EFD1BECC4A1008A7F35 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 3EEA4EDC1BECC4A1008A7F35 /* Project object */; +} diff --git a/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/SocialAppLayout-Inverted/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme similarity index 83% rename from examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme rename to examples/SocialAppLayout-Inverted/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme index ccd8f696bb..64285ed126 100644 --- a/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme +++ b/examples/SocialAppLayout-Inverted/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -1,6 +1,6 @@ @@ -32,8 +32,8 @@ @@ -51,15 +51,16 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + - + @@ -73,8 +74,8 @@ runnableDebuggingMode = "0"> diff --git a/examples/SocialAppLayout-Inverted/Sample/AppDelegate.h b/examples/SocialAppLayout-Inverted/Sample/AppDelegate.h new file mode 100644 index 0000000000..27e560aafe --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/AppDelegate.h @@ -0,0 +1,24 @@ +// +// AppDelegate.h +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/AppDelegate.m b/examples/SocialAppLayout-Inverted/Sample/AppDelegate.m new file mode 100644 index 0000000000..da7d93f4d8 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/AppDelegate.m @@ -0,0 +1,32 @@ +// +// AppDelegate.m +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "AppDelegate.h" +#import "ViewController.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]]; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/SocialAppLayout-Inverted/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..118c98f746 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/SocialAppLayout-Inverted/Sample/CommentsNode.h b/examples/SocialAppLayout-Inverted/Sample/CommentsNode.h new file mode 100644 index 0000000000..24e39cb5b4 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/CommentsNode.h @@ -0,0 +1,24 @@ +// +// CommentsNode.h +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import + +@interface CommentsNode : ASControlNode + +- (instancetype)initWithCommentsCount:(NSInteger)comentsCount; + +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/CommentsNode.m b/examples/SocialAppLayout-Inverted/Sample/CommentsNode.m new file mode 100644 index 0000000000..48d1d457ad --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/CommentsNode.m @@ -0,0 +1,70 @@ +// +// CommentsNode.m +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "CommentsNode.h" +#import "TextStyles.h" + +@interface CommentsNode () +@property (nonatomic, strong) ASImageNode *iconNode; +@property (nonatomic, strong) ASTextNode *countNode; +@property (nonatomic, assign) NSInteger commentsCount; +@end + +@implementation CommentsNode + +- (instancetype)initWithCommentsCount:(NSInteger)comentsCount +{ + self = [super init]; + if (self) { + _commentsCount = comentsCount; + + _iconNode = [[ASImageNode alloc] init]; + _iconNode.image = [UIImage imageNamed:@"icon_comment.png"]; + [self addSubnode:_iconNode]; + + _countNode = [[ASTextNode alloc] init]; + if (_commentsCount > 0) { + _countNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%zd", _commentsCount] attributes:[TextStyles cellControlStyle]]; + } + [self addSubnode:_countNode]; + + // make it tappable easily + self.hitTestSlop = UIEdgeInsetsMake(-10, -10, -10, -10); + } + + return self; + +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASStackLayoutSpec *mainStack = + [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:6.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children:@[_iconNode, _countNode]]; + + // Adjust size + mainStack.style.minWidth = ASDimensionMakeWithPoints(60.0); + mainStack.style.maxHeight = ASDimensionMakeWithPoints(40.0); + + return mainStack; +} + +@end diff --git a/examples/LayoutSpecPlayground/Default-568h@2x.png b/examples/SocialAppLayout-Inverted/Sample/Default-568h@2x.png similarity index 100% rename from examples/LayoutSpecPlayground/Default-568h@2x.png rename to examples/SocialAppLayout-Inverted/Sample/Default-568h@2x.png diff --git a/examples/LayoutSpecPlayground/Default-667h@2x.png b/examples/SocialAppLayout-Inverted/Sample/Default-667h@2x.png similarity index 100% rename from examples/LayoutSpecPlayground/Default-667h@2x.png rename to examples/SocialAppLayout-Inverted/Sample/Default-667h@2x.png diff --git a/examples/LayoutSpecPlayground/Default-736h@3x.png b/examples/SocialAppLayout-Inverted/Sample/Default-736h@3x.png similarity index 100% rename from examples/LayoutSpecPlayground/Default-736h@3x.png rename to examples/SocialAppLayout-Inverted/Sample/Default-736h@3x.png diff --git a/examples/LayoutSpecPlayground/Sample/Info.plist b/examples/SocialAppLayout-Inverted/Sample/Info.plist similarity index 95% rename from examples/LayoutSpecPlayground/Sample/Info.plist rename to examples/SocialAppLayout-Inverted/Sample/Info.plist index 14831a5ae5..ed1c9acf9b 100644 --- a/examples/LayoutSpecPlayground/Sample/Info.plist +++ b/examples/SocialAppLayout-Inverted/Sample/Info.plist @@ -18,10 +18,6 @@ 1.0 CFBundleSignature ???? - CFBundleURLTypes - - - CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/examples/SocialAppLayout-Inverted/Sample/LikesNode.h b/examples/SocialAppLayout-Inverted/Sample/LikesNode.h new file mode 100644 index 0000000000..b98cbebef6 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/LikesNode.h @@ -0,0 +1,24 @@ +// +// LikesNode.h +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import + +@interface LikesNode : ASControlNode + +- (instancetype)initWithLikesCount:(NSInteger)likesCount; + +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/LikesNode.m b/examples/SocialAppLayout-Inverted/Sample/LikesNode.m new file mode 100644 index 0000000000..593d5ae5de --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/LikesNode.m @@ -0,0 +1,83 @@ +// +// LikesNode.m +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "LikesNode.h" +#import "TextStyles.h" + +@interface LikesNode () +@property (nonatomic, strong) ASImageNode *iconNode; +@property (nonatomic, strong) ASTextNode *countNode; +@property (nonatomic, assign) NSInteger likesCount; +@property (nonatomic, assign) BOOL liked; +@end + +@implementation LikesNode + +- (instancetype)initWithLikesCount:(NSInteger)likesCount +{ + self = [super init]; + if (self) { + _likesCount = likesCount; + _liked = (_likesCount > 0) ? [LikesNode getYesOrNo] : NO; + + _iconNode = [[ASImageNode alloc] init]; + _iconNode.image = (_liked) ? [UIImage imageNamed:@"icon_liked.png"] : [UIImage imageNamed:@"icon_like.png"]; + [self addSubnode:_iconNode]; + + _countNode = [[ASTextNode alloc] init]; + if (_likesCount > 0) { + + NSDictionary *attributes = _liked ? [TextStyles cellControlColoredStyle] : [TextStyles cellControlStyle]; + _countNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%ld", (long)_likesCount] attributes:attributes]; + + } + [self addSubnode:_countNode]; + + // make it tappable easily + self.hitTestSlop = UIEdgeInsetsMake(-10, -10, -10, -10); + } + + return self; + +} + ++ (BOOL)getYesOrNo +{ + int tmp = (arc4random() % 30)+1; + if (tmp % 5 == 0) { + return YES; + } + return NO; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASStackLayoutSpec *mainStack = + [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:6.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children:@[_iconNode, _countNode]]; + + mainStack.style.minWidth = ASDimensionMakeWithPoints(60.0); + mainStack.style.maxHeight = ASDimensionMakeWithPoints(40.0); + + return mainStack; +} + +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/Post.h b/examples/SocialAppLayout-Inverted/Sample/Post.h new file mode 100644 index 0000000000..44bed0dd73 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/Post.h @@ -0,0 +1,33 @@ +// +// Post.h +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import + +@interface Post : NSObject + +@property (nonatomic, copy) NSString *username; +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *photo; +@property (nonatomic, copy) NSString *post; +@property (nonatomic, copy) NSString *time; +@property (nonatomic, copy) NSString *media; +@property (nonatomic, assign) NSInteger via; + +@property (nonatomic, assign) NSInteger likes; +@property (nonatomic, assign) NSInteger comments; + +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/Post.m b/examples/SocialAppLayout-Inverted/Sample/Post.m new file mode 100644 index 0000000000..10bf3a7623 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/Post.m @@ -0,0 +1,21 @@ +// +// Post.m +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "Post.h" + +@implementation Post +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/PostNode.h b/examples/SocialAppLayout-Inverted/Sample/PostNode.h new file mode 100644 index 0000000000..6d8c62696d --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/PostNode.h @@ -0,0 +1,26 @@ +// +// PostNode.h +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import + +@class Post; + +@interface PostNode : ASCellNode + +- (instancetype)initWithPost:(Post *)post; + +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/PostNode.m b/examples/SocialAppLayout-Inverted/Sample/PostNode.m new file mode 100644 index 0000000000..6323e2df01 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/PostNode.m @@ -0,0 +1,342 @@ +// +// PostNode.m +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "PostNode.h" +#import "Post.h" +#import "TextStyles.h" +#import "LikesNode.h" +#import "CommentsNode.h" + +#define PostNodeDividerColor [UIColor lightGrayColor] + +@interface PostNode() + +@property (strong, nonatomic) Post *post; +@property (strong, nonatomic) ASDisplayNode *divider; +@property (strong, nonatomic) ASTextNode *nameNode; +@property (strong, nonatomic) ASTextNode *usernameNode; +@property (strong, nonatomic) ASTextNode *timeNode; +@property (strong, nonatomic) ASTextNode *postNode; +@property (strong, nonatomic) ASImageNode *viaNode; +@property (strong, nonatomic) ASNetworkImageNode *avatarNode; +@property (strong, nonatomic) ASNetworkImageNode *mediaNode; +@property (strong, nonatomic) LikesNode *likesNode; +@property (strong, nonatomic) CommentsNode *commentsNode; +@property (strong, nonatomic) ASImageNode *optionsNode; + +@end + +@implementation PostNode + +#pragma mark - Lifecycle + +- (instancetype)initWithPost:(Post *)post +{ + self = [super init]; + if (self) { + _post = post; + + self.selectionStyle = UITableViewCellSelectionStyleNone; + + // Name node + _nameNode = [[ASTextNode alloc] init]; + _nameNode.attributedText = [[NSAttributedString alloc] initWithString:_post.name attributes:[TextStyles nameStyle]]; + _nameNode.maximumNumberOfLines = 1; + [self addSubnode:_nameNode]; + + // Username node + _usernameNode = [[ASTextNode alloc] init]; + _usernameNode.attributedText = [[NSAttributedString alloc] initWithString:_post.username attributes:[TextStyles usernameStyle]]; + _usernameNode.style.flexShrink = 1.0; //if name and username don't fit to cell width, allow username shrink + _usernameNode.truncationMode = NSLineBreakByTruncatingTail; + _usernameNode.maximumNumberOfLines = 1; + [self addSubnode:_usernameNode]; + + // Time node + _timeNode = [[ASTextNode alloc] init]; + _timeNode.attributedText = [[NSAttributedString alloc] initWithString:_post.time attributes:[TextStyles timeStyle]]; + [self addSubnode:_timeNode]; + + // Post node + _postNode = [[ASTextNode alloc] init]; + + // Processing URLs in post + NSString *kLinkAttributeName = @"TextLinkAttributeName"; + + if (![_post.post isEqualToString:@""]) { + + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:_post.post attributes:[TextStyles postStyle]]; + + NSDataDetector *urlDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil]; + + [urlDetector enumerateMatchesInString:attrString.string options:kNilOptions range:NSMakeRange(0, attrString.string.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop){ + + if (result.resultType == NSTextCheckingTypeLink) { + + NSMutableDictionary *linkAttributes = [[NSMutableDictionary alloc] initWithDictionary:[TextStyles postLinkStyle]]; + linkAttributes[kLinkAttributeName] = [NSURL URLWithString:result.URL.absoluteString]; + + [attrString addAttributes:linkAttributes range:result.range]; + + } + + }]; + + // Configure node to support tappable links + _postNode.delegate = self; + _postNode.userInteractionEnabled = YES; + _postNode.linkAttributeNames = @[ kLinkAttributeName ]; + _postNode.attributedText = attrString; + _postNode.passthroughNonlinkTouches = YES; // passes touches through when they aren't on a link + + } + + [self addSubnode:_postNode]; + + + // Media + if (![_post.media isEqualToString:@""]) { + + _mediaNode = [[ASNetworkImageNode alloc] init]; + _mediaNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); + _mediaNode.cornerRadius = 4.0; + _mediaNode.URL = [NSURL URLWithString:_post.media]; + _mediaNode.delegate = self; + _mediaNode.imageModificationBlock = ^UIImage *(UIImage *image) { + + UIImage *modifiedImage; + CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height); + + UIGraphicsBeginImageContextWithOptions(image.size, false, [[UIScreen mainScreen] scale]); + + [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:8.0] addClip]; + [image drawInRect:rect]; + modifiedImage = UIGraphicsGetImageFromCurrentImageContext(); + + UIGraphicsEndImageContext(); + + return modifiedImage; + + }; + [self addSubnode:_mediaNode]; + } + + // User pic + _avatarNode = [[ASNetworkImageNode alloc] init]; + _avatarNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); + _avatarNode.style.width = ASDimensionMakeWithPoints(44); + _avatarNode.style.height = ASDimensionMakeWithPoints(44); + _avatarNode.cornerRadius = 22.0; + _avatarNode.URL = [NSURL URLWithString:_post.photo]; + _avatarNode.imageModificationBlock = ^UIImage *(UIImage *image) { + + UIImage *modifiedImage; + CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height); + + UIGraphicsBeginImageContextWithOptions(image.size, false, [[UIScreen mainScreen] scale]); + + [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:44.0] addClip]; + [image drawInRect:rect]; + modifiedImage = UIGraphicsGetImageFromCurrentImageContext(); + + UIGraphicsEndImageContext(); + + return modifiedImage; + + }; + [self addSubnode:_avatarNode]; + + // Hairline cell separator + _divider = [[ASDisplayNode alloc] init]; + [self updateDividerColor]; + [self addSubnode:_divider]; + + // Via + if (_post.via != 0) { + _viaNode = [[ASImageNode alloc] init]; + _viaNode.image = (_post.via == 1) ? [UIImage imageNamed:@"icon_ios.png"] : [UIImage imageNamed:@"icon_android.png"]; + [self addSubnode:_viaNode]; + } + + // Bottom controls + _likesNode = [[LikesNode alloc] initWithLikesCount:_post.likes]; + [self addSubnode:_likesNode]; + + _commentsNode = [[CommentsNode alloc] initWithCommentsCount:_post.comments]; + [self addSubnode:_commentsNode]; + + _optionsNode = [[ASImageNode alloc] init]; + _optionsNode.image = [UIImage imageNamed:@"icon_more"]; + [self addSubnode:_optionsNode]; + + for (ASDisplayNode *node in self.subnodes) { + node.layerBacked = YES; + } + } + return self; +} + +- (void)updateDividerColor +{ + /* + * UITableViewCell traverses through all its descendant views and adjusts their background color accordingly + * either to [UIColor clearColor], although potentially it could use the same color as the selection highlight itself. + * After selection, the same trick is performed again in reverse, putting all the backgrounds back as they used to be. + * But in our case, we don't want to have the background color disappearing so we reset it after highlighting or + * selection is done. + */ + _divider.backgroundColor = PostNodeDividerColor; +} + +#pragma mark - ASDisplayNode + +- (void)didLoad +{ + // enable highlighting now that self.layer has loaded -- see ASHighlightOverlayLayer.h + self.layer.as_allowsHighlightDrawing = YES; + + [super didLoad]; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + // Flexible spacer between username and time + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; + spacer.style.flexGrow = 1.0; + + // Horizontal stack for name, username, via icon and time + NSMutableArray *layoutSpecChildren = [@[_nameNode, _usernameNode, spacer] mutableCopy]; + if (_post.via != 0) { + [layoutSpecChildren addObject:_viaNode]; + } + [layoutSpecChildren addObject:_timeNode]; + + ASStackLayoutSpec *nameStack = + [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:5.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children:layoutSpecChildren]; + nameStack.style.alignSelf = ASStackLayoutAlignSelfStretch; + + // bottom controls horizontal stack + ASStackLayoutSpec *controlsStack = + [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:10 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children:@[_likesNode, _commentsNode, _optionsNode]]; + + // Add more gaps for control line + controlsStack.style.spacingAfter = 3.0; + controlsStack.style.spacingBefore = 3.0; + + NSMutableArray *mainStackContent = [[NSMutableArray alloc] init]; + [mainStackContent addObject:nameStack]; + [mainStackContent addObject:_postNode]; + + + if (![_post.media isEqualToString:@""]){ + + // Only add the media node if an image is present + if (_mediaNode.image != nil) { + ASRatioLayoutSpec *imagePlace = + [ASRatioLayoutSpec + ratioLayoutSpecWithRatio:0.5 + child:_mediaNode]; + imagePlace.style.spacingAfter = 3.0; + imagePlace.style.spacingBefore = 3.0; + + [mainStackContent addObject:imagePlace]; + } + } + [mainStackContent addObject:controlsStack]; + + // Vertical spec of cell main content + ASStackLayoutSpec *contentSpec = + [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:8.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStretch + children:mainStackContent]; + contentSpec.style.flexShrink = 1.0; + + // Horizontal spec for avatar + ASStackLayoutSpec *avatarContentSpec = + [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:8.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + children:@[_avatarNode, contentSpec]]; + + return [ASInsetLayoutSpec + insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) + child:avatarContentSpec]; + +} + +- (void)layout +{ + [super layout]; + + // Manually layout the divider. + CGFloat pixelHeight = 1.0f / [[UIScreen mainScreen] scale]; + _divider.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, pixelHeight); +} + +#pragma mark - ASCellNode + +- (void)setHighlighted:(BOOL)highlighted +{ + [super setHighlighted:highlighted]; + + [self updateDividerColor]; +} + +- (void)setSelected:(BOOL)selected +{ + [super setSelected:selected]; + + [self updateDividerColor]; +} + +#pragma mark - + +- (BOOL)textNode:(ASTextNode *)richTextNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point +{ + // Opt into link highlighting -- tap and hold the link to try it! must enable highlighting on a layer, see -didLoad + return YES; +} + +- (void)textNode:(ASTextNode *)richTextNode tappedLinkAttribute:(NSString *)attribute value:(NSURL *)URL atPoint:(CGPoint)point textRange:(NSRange)textRange +{ + // The node tapped a link, open it + [[UIApplication sharedApplication] openURL:URL]; +} + +#pragma mark - ASNetworkImageNodeDelegate methods. + +- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image +{ + [self setNeedsLayout]; +} + +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/TextStyles.h b/examples/SocialAppLayout-Inverted/Sample/TextStyles.h new file mode 100644 index 0000000000..b8ef6780c1 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/TextStyles.h @@ -0,0 +1,31 @@ +// +// TextStyles.h +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import +#import + +@interface TextStyles : NSObject + ++ (NSDictionary *)nameStyle; ++ (NSDictionary *)usernameStyle; ++ (NSDictionary *)timeStyle; ++ (NSDictionary *)postStyle; ++ (NSDictionary *)postLinkStyle; ++ (NSDictionary *)cellControlStyle; ++ (NSDictionary *)cellControlColoredStyle; + +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/TextStyles.m b/examples/SocialAppLayout-Inverted/Sample/TextStyles.m new file mode 100644 index 0000000000..8f642522b7 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/TextStyles.m @@ -0,0 +1,79 @@ +// +// TextStyles.m +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "TextStyles.h" + +@implementation TextStyles + ++ (NSDictionary *)nameStyle +{ + return @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:15.0], + NSForegroundColorAttributeName: [UIColor blackColor] + }; +} + ++ (NSDictionary *)usernameStyle +{ + return @{ + NSFontAttributeName : [UIFont systemFontOfSize:13.0], + NSForegroundColorAttributeName: [UIColor lightGrayColor] + }; +} + ++ (NSDictionary *)timeStyle +{ + return @{ + NSFontAttributeName : [UIFont systemFontOfSize:13.0], + NSForegroundColorAttributeName: [UIColor grayColor] + }; +} + ++ (NSDictionary *)postStyle +{ + return @{ + NSFontAttributeName : [UIFont systemFontOfSize:15.0], + NSForegroundColorAttributeName: [UIColor blackColor] + }; +} + ++ (NSDictionary *)postLinkStyle +{ + return @{ + NSFontAttributeName : [UIFont systemFontOfSize:15.0], + NSForegroundColorAttributeName: [UIColor colorWithRed:59.0/255.0 green:89.0/255.0 blue:152.0/255.0 alpha:1.0], + NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle) + }; +} + ++ (NSDictionary *)cellControlStyle +{ + return @{ + NSFontAttributeName : [UIFont systemFontOfSize:13.0], + NSForegroundColorAttributeName: [UIColor lightGrayColor] + }; +} + ++ (NSDictionary *)cellControlColoredStyle +{ + return @{ + NSFontAttributeName : [UIFont systemFontOfSize:13.0], + NSForegroundColorAttributeName: [UIColor colorWithRed:59.0/255.0 green:89.0/255.0 blue:152.0/255.0 alpha:1.0] + }; +} + +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/ViewController.h b/examples/SocialAppLayout-Inverted/Sample/ViewController.h new file mode 100644 index 0000000000..bfb359c6b5 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/ViewController.h @@ -0,0 +1,21 @@ +// +// ViewController.h +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import + +@interface ViewController : ASViewController +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/ViewController.m b/examples/SocialAppLayout-Inverted/Sample/ViewController.m new file mode 100644 index 0000000000..6bc0d48a2d --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/ViewController.m @@ -0,0 +1,152 @@ +// +// ViewController.m +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "ViewController.h" +#import "Post.h" +#import "PostNode.h" + +#import +#import + +#include + +@interface ViewController () + +@property (nonatomic, strong) ASTableNode *tableNode; +@property (nonatomic, strong) NSMutableArray *socialAppDataSource; + +@end + +#pragma mark - Lifecycle + +@implementation ViewController + +- (instancetype)init +{ + _tableNode = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain]; + _tableNode.inverted = YES; + self = [super initWithNode:_tableNode]; + + + if (self) { + + _tableNode.delegate = self; + _tableNode.dataSource = self; + self.title = @"Timeline"; + + [self createSocialAppDataSource]; + } + + return self; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + CGFloat inset = [self topBarsHeight]; + self.tableNode.view.contentInset = UIEdgeInsetsMake(-inset, 0, inset, 0); + self.tableNode.view.scrollIndicatorInsets = UIEdgeInsetsMake(-inset, 0, inset, 0); +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // SocialAppNode has its own separator + self.tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; +} + +- (CGFloat)topBarsHeight +{ + // No need to adjust if the edge isn't available + if ((self.edgesForExtendedLayout & UIRectEdgeTop) == 0) { + return 0.0; + } + return CGRectGetHeight(self.navigationController.navigationBar.frame) + CGRectGetHeight([UIApplication sharedApplication].statusBarFrame); +} + + +#pragma mark - Data Model + +- (void)createSocialAppDataSource +{ + _socialAppDataSource = [[NSMutableArray alloc] init]; + + Post *newPost = [[Post alloc] init]; + newPost.name = @"Apple Guy"; + newPost.username = @"@appleguy"; + newPost.photo = @"https://avatars1.githubusercontent.com/u/565251?v=3&s=96"; + newPost.post = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."; + newPost.time = @"3s"; + newPost.media = @""; + newPost.via = 0; + newPost.likes = arc4random_uniform(74); + newPost.comments = arc4random_uniform(40); + [_socialAppDataSource addObject:newPost]; + + newPost = [[Post alloc] init]; + newPost.name = @"Huy Nguyen"; + newPost.username = @"@nguyenhuy"; + newPost.photo = @"https://avatars2.githubusercontent.com/u/587874?v=3&s=96"; + newPost.post = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + newPost.time = @"1m"; + newPost.media = @""; + newPost.via = 1; + newPost.likes = arc4random_uniform(74); + newPost.comments = arc4random_uniform(40); + [_socialAppDataSource addObject:newPost]; + + newPost = [[Post alloc] init]; + newPost.name = @"Alex Long Name"; + newPost.username = @"@veryyyylongusername"; + newPost.photo = @"https://avatars1.githubusercontent.com/u/8086633?v=3&s=96"; + newPost.post = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + newPost.time = @"3:02"; + newPost.media = @"http://www.ngmag.ru/upload/iblock/f93/f9390efc34151456598077c1ba44a94d.jpg"; + newPost.via = 2; + newPost.likes = arc4random_uniform(74); + newPost.comments = arc4random_uniform(40); + [_socialAppDataSource addObject:newPost]; + + newPost = [[Post alloc] init]; + newPost.name = @"Vitaly Baev"; + newPost.username = @"@vitalybaev"; + newPost.photo = @"https://avatars0.githubusercontent.com/u/724423?v=3&s=96"; + newPost.post = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. https://github.com/facebook/AsyncDisplayKit Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + newPost.time = @"yesterday"; + newPost.media = @""; + newPost.via = 1; + newPost.likes = arc4random_uniform(74); + newPost.comments = arc4random_uniform(40); + [_socialAppDataSource addObject:newPost]; +} + +#pragma mark - ASTableNode + +- (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath +{ + Post *post = self.socialAppDataSource[indexPath.row]; + return ^{ + return [[PostNode alloc] initWithPost:post]; + }; +} + +- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section +{ + return self.socialAppDataSource.count; +} + +@end diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_android.png b/examples/SocialAppLayout-Inverted/Sample/icon_android.png new file mode 100644 index 0000000000..6d30985339 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_android.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_android@2x.png b/examples/SocialAppLayout-Inverted/Sample/icon_android@2x.png new file mode 100644 index 0000000000..c0dd2f5977 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_android@2x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_android@3x.png b/examples/SocialAppLayout-Inverted/Sample/icon_android@3x.png new file mode 100644 index 0000000000..d3e83e9334 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_android@3x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_comment.png b/examples/SocialAppLayout-Inverted/Sample/icon_comment.png new file mode 100644 index 0000000000..59ccfe43c1 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_comment.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_comment@2x.png b/examples/SocialAppLayout-Inverted/Sample/icon_comment@2x.png new file mode 100644 index 0000000000..bedd0593c0 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_comment@2x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_comment@3x.png b/examples/SocialAppLayout-Inverted/Sample/icon_comment@3x.png new file mode 100644 index 0000000000..eb8a0d7660 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_comment@3x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_ios.png b/examples/SocialAppLayout-Inverted/Sample/icon_ios.png new file mode 100644 index 0000000000..0cd417d446 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_ios.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_ios@2x.png b/examples/SocialAppLayout-Inverted/Sample/icon_ios@2x.png new file mode 100644 index 0000000000..f73561fd09 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_ios@2x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_ios@3x.png b/examples/SocialAppLayout-Inverted/Sample/icon_ios@3x.png new file mode 100644 index 0000000000..35d2bb2b37 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_ios@3x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_like.png b/examples/SocialAppLayout-Inverted/Sample/icon_like.png new file mode 100644 index 0000000000..43110b9d59 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_like.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_like@2x.png b/examples/SocialAppLayout-Inverted/Sample/icon_like@2x.png new file mode 100644 index 0000000000..1b535d748b Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_like@2x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_like@3x.png b/examples/SocialAppLayout-Inverted/Sample/icon_like@3x.png new file mode 100644 index 0000000000..8c80507335 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_like@3x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_liked.png b/examples/SocialAppLayout-Inverted/Sample/icon_liked.png new file mode 100644 index 0000000000..b1c1ade901 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_liked.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_liked@2x.png b/examples/SocialAppLayout-Inverted/Sample/icon_liked@2x.png new file mode 100644 index 0000000000..d9dc5988ea Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_liked@2x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_liked@3x.png b/examples/SocialAppLayout-Inverted/Sample/icon_liked@3x.png new file mode 100644 index 0000000000..00578ac63e Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_liked@3x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_more.png b/examples/SocialAppLayout-Inverted/Sample/icon_more.png new file mode 100644 index 0000000000..013126d291 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_more.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_more@2x.png b/examples/SocialAppLayout-Inverted/Sample/icon_more@2x.png new file mode 100644 index 0000000000..3d183df436 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_more@2x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/icon_more@3x.png b/examples/SocialAppLayout-Inverted/Sample/icon_more@3x.png new file mode 100644 index 0000000000..d5f829ab11 Binary files /dev/null and b/examples/SocialAppLayout-Inverted/Sample/icon_more@3x.png differ diff --git a/examples/SocialAppLayout-Inverted/Sample/main.m b/examples/SocialAppLayout-Inverted/Sample/main.m new file mode 100644 index 0000000000..791ef4b743 --- /dev/null +++ b/examples/SocialAppLayout-Inverted/Sample/main.m @@ -0,0 +1,25 @@ +// +// main.m +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/SocialAppLayout/Podfile b/examples/SocialAppLayout/Podfile index 919de4b311..defaf55058 100644 --- a/examples/SocialAppLayout/Podfile +++ b/examples/SocialAppLayout/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj b/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj index 988eb17a45..abd0cb6653 100644 --- a/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj +++ b/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj @@ -1,1311 +1,480 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 193CE60FE6429EFEBF6EA52B - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - 21F2C1D9B53F9468EAF1653F - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - 3EEA4EDB1BECC4A1008A7F35 - - children - - 3EEA4EE61BECC4A1008A7F35 - 3EEA4EE51BECC4A1008A7F35 - 842ADAFE88475D19B24183AC - EED34FA6D8171DF44757C852 - - isa - PBXGroup - sourceTree - <group> - - 3EEA4EDC1BECC4A1008A7F35 - - attributes - - LastUpgradeCheck - 0710 - ORGANIZATIONNAME - Facebook - TargetAttributes - - 3EEA4EE31BECC4A1008A7F35 - - CreatedOnToolsVersion - 7.1 - - - - buildConfigurationList - 3EEA4EDF1BECC4A1008A7F35 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - 3EEA4EDB1BECC4A1008A7F35 - productRefGroup - 3EEA4EE51BECC4A1008A7F35 - projectDirPath - - projectReferences - - projectRoot - - targets - - 3EEA4EE31BECC4A1008A7F35 - - - 3EEA4EDF1BECC4A1008A7F35 - - buildConfigurations - - 3EEA4EF91BECC4A1008A7F35 - 3EEA4EFA1BECC4A1008A7F35 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 3EEA4EE01BECC4A1008A7F35 - - buildActionMask - 2147483647 - files - - 3EEA4EEF1BECC4A1008A7F35 - 3EEA4EEC1BECC4A1008A7F35 - 3EEA4EE91BECC4A1008A7F35 - 3EEA4F061BECC6C9008A7F35 - 3EEA4F0C1BECCA0A008A7F35 - 3EEA4F381BECE775008A7F35 - 3EEA4F091BECC855008A7F35 - 3EEA4F1D1BECE358008A7F35 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 3EEA4EE11BECC4A1008A7F35 - - buildActionMask - 2147483647 - files - - 93964C9FCC28D92625106430 - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 3EEA4EE21BECC4A1008A7F35 - - buildActionMask - 2147483647 - files - - 3EEA4F2A1BECE440008A7F35 - 3EEA4F181BECDCD6008A7F35 - 3EEA4F311BECE440008A7F35 - 3EEA4F3D1BECE99F008A7F35 - 3EEA4F141BECDCD6008A7F35 - 3EEA4F3E1BECE99F008A7F35 - 3EEA4F2B1BECE440008A7F35 - 3EEA4F351BECE440008A7F35 - 3EEA4EF41BECC4A1008A7F35 - 3EEA4F171BECDCD6008A7F35 - 3EEA4F021BECC4E8008A7F35 - 3EEA4F161BECDCD6008A7F35 - 3EEA4F191BECDCD6008A7F35 - 3EEA4F331BECE440008A7F35 - 3EEA4F341BECE440008A7F35 - 3EEA4F321BECE440008A7F35 - 3EEA4F151BECDCD6008A7F35 - 3EEA4F301BECE440008A7F35 - 3EEA4F2C1BECE440008A7F35 - 3EEA4F011BECC4E8008A7F35 - 3EEA4F031BECC4E8008A7F35 - 3EEA4F3C1BECE99F008A7F35 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 3EEA4EE31BECC4A1008A7F35 - - buildConfigurationList - 3EEA4EFB1BECC4A1008A7F35 - buildPhases - - B5BD9E5609B2CB179EEE0CF4 - 3EEA4EE01BECC4A1008A7F35 - 3EEA4EE11BECC4A1008A7F35 - 3EEA4EE21BECC4A1008A7F35 - 21F2C1D9B53F9468EAF1653F - 852437589F1D53B9483A75DF - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - 3EEA4EE41BECC4A1008A7F35 - productType - com.apple.product-type.application - - 3EEA4EE41BECC4A1008A7F35 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - 3EEA4EE51BECC4A1008A7F35 - - children - - 3EEA4EE41BECC4A1008A7F35 - - isa - PBXGroup - name - Products - sourceTree - <group> - - 3EEA4EE61BECC4A1008A7F35 - - children - - 3EEA4F0D1BECDCA6008A7F35 - 3EEA4EEA1BECC4A1008A7F35 - 3EEA4EEB1BECC4A1008A7F35 - 3EEA4EED1BECC4A1008A7F35 - 3EEA4EEE1BECC4A1008A7F35 - 3EEA4EF31BECC4A1008A7F35 - 3EEA4EF81BECC4A1008A7F35 - 3EEA4EE71BECC4A1008A7F35 - 3EEA4F041BECC6C9008A7F35 - 3EEA4F051BECC6C9008A7F35 - 3EEA4F071BECC855008A7F35 - 3EEA4F081BECC855008A7F35 - 3EEA4F0A1BECCA0A008A7F35 - 3EEA4F0B1BECCA0A008A7F35 - 3EEA4F1B1BECE358008A7F35 - 3EEA4F1C1BECE358008A7F35 - 3EEA4F361BECE775008A7F35 - 3EEA4F371BECE775008A7F35 - - isa - PBXGroup - path - Sample - sourceTree - <group> - - 3EEA4EE71BECC4A1008A7F35 - - children - - 3EEA4EFE1BECC4E8008A7F35 - 3EEA4EFF1BECC4E8008A7F35 - 3EEA4F001BECC4E8008A7F35 - 3EEA4EE81BECC4A1008A7F35 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - 3EEA4EE81BECC4A1008A7F35 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - 3EEA4EE91BECC4A1008A7F35 - - fileRef - 3EEA4EE81BECC4A1008A7F35 - isa - PBXBuildFile - - 3EEA4EEA1BECC4A1008A7F35 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - 3EEA4EEB1BECC4A1008A7F35 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - 3EEA4EEC1BECC4A1008A7F35 - - fileRef - 3EEA4EEB1BECC4A1008A7F35 - isa - PBXBuildFile - - 3EEA4EED1BECC4A1008A7F35 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - 3EEA4EEE1BECC4A1008A7F35 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - 3EEA4EEF1BECC4A1008A7F35 - - fileRef - 3EEA4EEE1BECC4A1008A7F35 - isa - PBXBuildFile - - 3EEA4EF31BECC4A1008A7F35 - - isa - PBXFileReference - lastKnownFileType - folder.assetcatalog - path - Assets.xcassets - sourceTree - <group> - - 3EEA4EF41BECC4A1008A7F35 - - fileRef - 3EEA4EF31BECC4A1008A7F35 - isa - PBXBuildFile - - 3EEA4EF81BECC4A1008A7F35 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - 3EEA4EF91BECC4A1008A7F35 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - DEBUG_INFORMATION_FORMAT - dwarf - ENABLE_STRICT_OBJC_MSGSEND - YES - ENABLE_TESTABILITY - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_NO_COMMON_BLOCKS - YES - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 9.1 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - - isa - XCBuildConfiguration - name - Debug - - 3EEA4EFA1BECC4A1008A7F35 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - DEBUG_INFORMATION_FORMAT - dwarf-with-dsym - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_NO_COMMON_BLOCKS - YES - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 9.1 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - 3EEA4EFB1BECC4A1008A7F35 - - buildConfigurations - - 3EEA4EFC1BECC4A1008A7F35 - 3EEA4EFD1BECC4A1008A7F35 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 3EEA4EFC1BECC4A1008A7F35 - - baseConfigurationReference - CC6F2ABE8383FAB21802C734 - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_BUNDLE_IDENTIFIER - com.facebook.AsyncDisplayKit.Sample - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Debug - - 3EEA4EFD1BECC4A1008A7F35 - - baseConfigurationReference - FCCC1AD413FCA8603156ED15 - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_BUNDLE_IDENTIFIER - com.facebook.AsyncDisplayKit.Sample - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Release - - 3EEA4EFE1BECC4E8008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-568h@2x.png - sourceTree - <group> - - 3EEA4EFF1BECC4E8008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-667h@2x.png - sourceTree - <group> - - 3EEA4F001BECC4E8008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-736h@3x.png - sourceTree - <group> - - 3EEA4F011BECC4E8008A7F35 - - fileRef - 3EEA4EFE1BECC4E8008A7F35 - isa - PBXBuildFile - - 3EEA4F021BECC4E8008A7F35 - - fileRef - 3EEA4EFF1BECC4E8008A7F35 - isa - PBXBuildFile - - 3EEA4F031BECC4E8008A7F35 - - fileRef - 3EEA4F001BECC4E8008A7F35 - isa - PBXBuildFile - - 3EEA4F041BECC6C9008A7F35 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - Post.h - sourceTree - <group> - - 3EEA4F051BECC6C9008A7F35 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - Post.m - sourceTree - <group> - - 3EEA4F061BECC6C9008A7F35 - - fileRef - 3EEA4F051BECC6C9008A7F35 - isa - PBXBuildFile - - 3EEA4F071BECC855008A7F35 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - PostNode.h - sourceTree - <group> - - 3EEA4F081BECC855008A7F35 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - PostNode.m - sourceTree - <group> - - 3EEA4F091BECC855008A7F35 - - fileRef - 3EEA4F081BECC855008A7F35 - isa - PBXBuildFile - - 3EEA4F0A1BECCA0A008A7F35 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - TextStyles.h - sourceTree - <group> - - 3EEA4F0B1BECCA0A008A7F35 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - TextStyles.m - sourceTree - <group> - - 3EEA4F0C1BECCA0A008A7F35 - - fileRef - 3EEA4F0B1BECCA0A008A7F35 - isa - PBXBuildFile - - 3EEA4F0D1BECDCA6008A7F35 - - children - - 3EEA4F391BECE99F008A7F35 - 3EEA4F3A1BECE99F008A7F35 - 3EEA4F3B1BECE99F008A7F35 - 3EEA4F1E1BECE440008A7F35 - 3EEA4F1F1BECE440008A7F35 - 3EEA4F201BECE440008A7F35 - 3EEA4F241BECE440008A7F35 - 3EEA4F251BECE440008A7F35 - 3EEA4F261BECE440008A7F35 - 3EEA4F271BECE440008A7F35 - 3EEA4F281BECE440008A7F35 - 3EEA4F291BECE440008A7F35 - 3EEA4F0E1BECDCD6008A7F35 - 3EEA4F0F1BECDCD6008A7F35 - 3EEA4F101BECDCD6008A7F35 - 3EEA4F111BECDCD6008A7F35 - 3EEA4F121BECDCD6008A7F35 - 3EEA4F131BECDCD6008A7F35 - - isa - PBXGroup - name - Images - sourceTree - <group> - - 3EEA4F0E1BECDCD6008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_android.png - sourceTree - <group> - - 3EEA4F0F1BECDCD6008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_android@2x.png - sourceTree - <group> - - 3EEA4F101BECDCD6008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_android@3x.png - sourceTree - <group> - - 3EEA4F111BECDCD6008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_ios.png - sourceTree - <group> - - 3EEA4F121BECDCD6008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_ios@2x.png - sourceTree - <group> - - 3EEA4F131BECDCD6008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_ios@3x.png - sourceTree - <group> - - 3EEA4F141BECDCD6008A7F35 - - fileRef - 3EEA4F0E1BECDCD6008A7F35 - isa - PBXBuildFile - - 3EEA4F151BECDCD6008A7F35 - - fileRef - 3EEA4F0F1BECDCD6008A7F35 - isa - PBXBuildFile - - 3EEA4F161BECDCD6008A7F35 - - fileRef - 3EEA4F101BECDCD6008A7F35 - isa - PBXBuildFile - - 3EEA4F171BECDCD6008A7F35 - - fileRef - 3EEA4F111BECDCD6008A7F35 - isa - PBXBuildFile - - 3EEA4F181BECDCD6008A7F35 - - fileRef - 3EEA4F121BECDCD6008A7F35 - isa - PBXBuildFile - - 3EEA4F191BECDCD6008A7F35 - - fileRef - 3EEA4F131BECDCD6008A7F35 - isa - PBXBuildFile - - 3EEA4F1B1BECE358008A7F35 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - LikesNode.h - sourceTree - <group> - - 3EEA4F1C1BECE358008A7F35 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - LikesNode.m - sourceTree - <group> - - 3EEA4F1D1BECE358008A7F35 - - fileRef - 3EEA4F1C1BECE358008A7F35 - isa - PBXBuildFile - - 3EEA4F1E1BECE440008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_liked.png - sourceTree - <group> - - 3EEA4F1F1BECE440008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_liked@2x.png - sourceTree - <group> - - 3EEA4F201BECE440008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_liked@3x.png - sourceTree - <group> - - 3EEA4F241BECE440008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_like@3x.png - sourceTree - <group> - - 3EEA4F251BECE440008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_like@2x.png - sourceTree - <group> - - 3EEA4F261BECE440008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_like.png - sourceTree - <group> - - 3EEA4F271BECE440008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_comment@3x.png - sourceTree - <group> - - 3EEA4F281BECE440008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_comment@2x.png - sourceTree - <group> - - 3EEA4F291BECE440008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_comment.png - sourceTree - <group> - - 3EEA4F2A1BECE440008A7F35 - - fileRef - 3EEA4F1E1BECE440008A7F35 - isa - PBXBuildFile - - 3EEA4F2B1BECE440008A7F35 - - fileRef - 3EEA4F1F1BECE440008A7F35 - isa - PBXBuildFile - - 3EEA4F2C1BECE440008A7F35 - - fileRef - 3EEA4F201BECE440008A7F35 - isa - PBXBuildFile - - 3EEA4F301BECE440008A7F35 - - fileRef - 3EEA4F241BECE440008A7F35 - isa - PBXBuildFile - - 3EEA4F311BECE440008A7F35 - - fileRef - 3EEA4F251BECE440008A7F35 - isa - PBXBuildFile - - 3EEA4F321BECE440008A7F35 - - fileRef - 3EEA4F261BECE440008A7F35 - isa - PBXBuildFile - - 3EEA4F331BECE440008A7F35 - - fileRef - 3EEA4F271BECE440008A7F35 - isa - PBXBuildFile - - 3EEA4F341BECE440008A7F35 - - fileRef - 3EEA4F281BECE440008A7F35 - isa - PBXBuildFile - - 3EEA4F351BECE440008A7F35 - - fileRef - 3EEA4F291BECE440008A7F35 - isa - PBXBuildFile - - 3EEA4F361BECE775008A7F35 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - CommentsNode.h - sourceTree - <group> - - 3EEA4F371BECE775008A7F35 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - CommentsNode.m - sourceTree - <group> - - 3EEA4F381BECE775008A7F35 - - fileRef - 3EEA4F371BECE775008A7F35 - isa - PBXBuildFile - - 3EEA4F391BECE99F008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_more.png - sourceTree - <group> - - 3EEA4F3A1BECE99F008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_more@2x.png - sourceTree - <group> - - 3EEA4F3B1BECE99F008A7F35 - - isa - PBXFileReference - lastKnownFileType - image.png - path - icon_more@3x.png - sourceTree - <group> - - 3EEA4F3C1BECE99F008A7F35 - - fileRef - 3EEA4F391BECE99F008A7F35 - isa - PBXBuildFile - - 3EEA4F3D1BECE99F008A7F35 - - fileRef - 3EEA4F3A1BECE99F008A7F35 - isa - PBXBuildFile - - 3EEA4F3E1BECE99F008A7F35 - - fileRef - 3EEA4F3B1BECE99F008A7F35 - isa - PBXBuildFile - - 842ADAFE88475D19B24183AC - - children - - CC6F2ABE8383FAB21802C734 - FCCC1AD413FCA8603156ED15 - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 852437589F1D53B9483A75DF - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - 93964C9FCC28D92625106430 - - fileRef - 193CE60FE6429EFEBF6EA52B - isa - PBXBuildFile - - B5BD9E5609B2CB179EEE0CF4 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - CC6F2ABE8383FAB21802C734 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - EED34FA6D8171DF44757C852 - - children - - 193CE60FE6429EFEBF6EA52B - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - FCCC1AD413FCA8603156ED15 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - - rootObject - 3EEA4EDC1BECC4A1008A7F35 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 3EEA4EE91BECC4A1008A7F35 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4EE81BECC4A1008A7F35 /* main.m */; }; + 3EEA4EEC1BECC4A1008A7F35 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4EEB1BECC4A1008A7F35 /* AppDelegate.m */; }; + 3EEA4EEF1BECC4A1008A7F35 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4EEE1BECC4A1008A7F35 /* ViewController.m */; }; + 3EEA4EF41BECC4A1008A7F35 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4EF31BECC4A1008A7F35 /* Assets.xcassets */; }; + 3EEA4F011BECC4E8008A7F35 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4EFE1BECC4E8008A7F35 /* Default-568h@2x.png */; }; + 3EEA4F021BECC4E8008A7F35 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4EFF1BECC4E8008A7F35 /* Default-667h@2x.png */; }; + 3EEA4F031BECC4E8008A7F35 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F001BECC4E8008A7F35 /* Default-736h@3x.png */; }; + 3EEA4F061BECC6C9008A7F35 /* Post.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F051BECC6C9008A7F35 /* Post.m */; }; + 3EEA4F091BECC855008A7F35 /* PostNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F081BECC855008A7F35 /* PostNode.m */; }; + 3EEA4F0C1BECCA0A008A7F35 /* TextStyles.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F0B1BECCA0A008A7F35 /* TextStyles.m */; }; + 3EEA4F141BECDCD6008A7F35 /* icon_android.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F0E1BECDCD6008A7F35 /* icon_android.png */; }; + 3EEA4F151BECDCD6008A7F35 /* icon_android@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F0F1BECDCD6008A7F35 /* icon_android@2x.png */; }; + 3EEA4F161BECDCD6008A7F35 /* icon_android@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F101BECDCD6008A7F35 /* icon_android@3x.png */; }; + 3EEA4F171BECDCD6008A7F35 /* icon_ios.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F111BECDCD6008A7F35 /* icon_ios.png */; }; + 3EEA4F181BECDCD6008A7F35 /* icon_ios@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F121BECDCD6008A7F35 /* icon_ios@2x.png */; }; + 3EEA4F191BECDCD6008A7F35 /* icon_ios@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F131BECDCD6008A7F35 /* icon_ios@3x.png */; }; + 3EEA4F1D1BECE358008A7F35 /* LikesNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F1C1BECE358008A7F35 /* LikesNode.m */; }; + 3EEA4F2A1BECE440008A7F35 /* icon_liked.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F1E1BECE440008A7F35 /* icon_liked.png */; }; + 3EEA4F2B1BECE440008A7F35 /* icon_liked@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F1F1BECE440008A7F35 /* icon_liked@2x.png */; }; + 3EEA4F2C1BECE440008A7F35 /* icon_liked@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F201BECE440008A7F35 /* icon_liked@3x.png */; }; + 3EEA4F301BECE440008A7F35 /* icon_like@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F241BECE440008A7F35 /* icon_like@3x.png */; }; + 3EEA4F311BECE440008A7F35 /* icon_like@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F251BECE440008A7F35 /* icon_like@2x.png */; }; + 3EEA4F321BECE440008A7F35 /* icon_like.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F261BECE440008A7F35 /* icon_like.png */; }; + 3EEA4F331BECE440008A7F35 /* icon_comment@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F271BECE440008A7F35 /* icon_comment@3x.png */; }; + 3EEA4F341BECE440008A7F35 /* icon_comment@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F281BECE440008A7F35 /* icon_comment@2x.png */; }; + 3EEA4F351BECE440008A7F35 /* icon_comment.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F291BECE440008A7F35 /* icon_comment.png */; }; + 3EEA4F381BECE775008A7F35 /* CommentsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EEA4F371BECE775008A7F35 /* CommentsNode.m */; }; + 3EEA4F3C1BECE99F008A7F35 /* icon_more.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F391BECE99F008A7F35 /* icon_more.png */; }; + 3EEA4F3D1BECE99F008A7F35 /* icon_more@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F3A1BECE99F008A7F35 /* icon_more@2x.png */; }; + 3EEA4F3E1BECE99F008A7F35 /* icon_more@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3EEA4F3B1BECE99F008A7F35 /* icon_more@3x.png */; }; + 93964C9FCC28D92625106430 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 193CE60FE6429EFEBF6EA52B /* libPods-Sample.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 193CE60FE6429EFEBF6EA52B /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3EEA4EE41BECC4A1008A7F35 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 3EEA4EE81BECC4A1008A7F35 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 3EEA4EEA1BECC4A1008A7F35 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 3EEA4EEB1BECC4A1008A7F35 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 3EEA4EED1BECC4A1008A7F35 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 3EEA4EEE1BECC4A1008A7F35 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 3EEA4EF31BECC4A1008A7F35 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 3EEA4EF81BECC4A1008A7F35 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3EEA4EFE1BECC4E8008A7F35 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; + 3EEA4EFF1BECC4E8008A7F35 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = ""; }; + 3EEA4F001BECC4E8008A7F35 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = ""; }; + 3EEA4F041BECC6C9008A7F35 /* Post.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Post.h; sourceTree = ""; }; + 3EEA4F051BECC6C9008A7F35 /* Post.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Post.m; sourceTree = ""; }; + 3EEA4F071BECC855008A7F35 /* PostNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PostNode.h; sourceTree = ""; }; + 3EEA4F081BECC855008A7F35 /* PostNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostNode.m; sourceTree = ""; }; + 3EEA4F0A1BECCA0A008A7F35 /* TextStyles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextStyles.h; sourceTree = ""; }; + 3EEA4F0B1BECCA0A008A7F35 /* TextStyles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TextStyles.m; sourceTree = ""; }; + 3EEA4F0E1BECDCD6008A7F35 /* icon_android.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_android.png; sourceTree = ""; }; + 3EEA4F0F1BECDCD6008A7F35 /* icon_android@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_android@2x.png"; sourceTree = ""; }; + 3EEA4F101BECDCD6008A7F35 /* icon_android@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_android@3x.png"; sourceTree = ""; }; + 3EEA4F111BECDCD6008A7F35 /* icon_ios.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_ios.png; sourceTree = ""; }; + 3EEA4F121BECDCD6008A7F35 /* icon_ios@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_ios@2x.png"; sourceTree = ""; }; + 3EEA4F131BECDCD6008A7F35 /* icon_ios@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_ios@3x.png"; sourceTree = ""; }; + 3EEA4F1B1BECE358008A7F35 /* LikesNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LikesNode.h; sourceTree = ""; }; + 3EEA4F1C1BECE358008A7F35 /* LikesNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LikesNode.m; sourceTree = ""; }; + 3EEA4F1E1BECE440008A7F35 /* icon_liked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_liked.png; sourceTree = ""; }; + 3EEA4F1F1BECE440008A7F35 /* icon_liked@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_liked@2x.png"; sourceTree = ""; }; + 3EEA4F201BECE440008A7F35 /* icon_liked@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_liked@3x.png"; sourceTree = ""; }; + 3EEA4F241BECE440008A7F35 /* icon_like@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_like@3x.png"; sourceTree = ""; }; + 3EEA4F251BECE440008A7F35 /* icon_like@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_like@2x.png"; sourceTree = ""; }; + 3EEA4F261BECE440008A7F35 /* icon_like.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_like.png; sourceTree = ""; }; + 3EEA4F271BECE440008A7F35 /* icon_comment@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_comment@3x.png"; sourceTree = ""; }; + 3EEA4F281BECE440008A7F35 /* icon_comment@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_comment@2x.png"; sourceTree = ""; }; + 3EEA4F291BECE440008A7F35 /* icon_comment.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_comment.png; sourceTree = ""; }; + 3EEA4F361BECE775008A7F35 /* CommentsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentsNode.h; sourceTree = ""; }; + 3EEA4F371BECE775008A7F35 /* CommentsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentsNode.m; sourceTree = ""; }; + 3EEA4F391BECE99F008A7F35 /* icon_more.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_more.png; sourceTree = ""; }; + 3EEA4F3A1BECE99F008A7F35 /* icon_more@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_more@2x.png"; sourceTree = ""; }; + 3EEA4F3B1BECE99F008A7F35 /* icon_more@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_more@3x.png"; sourceTree = ""; }; + CC6F2ABE8383FAB21802C734 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + FCCC1AD413FCA8603156ED15 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3EEA4EE11BECC4A1008A7F35 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 93964C9FCC28D92625106430 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3EEA4EDB1BECC4A1008A7F35 = { + isa = PBXGroup; + children = ( + 3EEA4EE61BECC4A1008A7F35 /* Sample */, + 3EEA4EE51BECC4A1008A7F35 /* Products */, + 842ADAFE88475D19B24183AC /* Pods */, + EED34FA6D8171DF44757C852 /* Frameworks */, + ); + sourceTree = ""; + }; + 3EEA4EE51BECC4A1008A7F35 /* Products */ = { + isa = PBXGroup; + children = ( + 3EEA4EE41BECC4A1008A7F35 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 3EEA4EE61BECC4A1008A7F35 /* Sample */ = { + isa = PBXGroup; + children = ( + 3EEA4F0D1BECDCA6008A7F35 /* Images */, + 3EEA4EEA1BECC4A1008A7F35 /* AppDelegate.h */, + 3EEA4EEB1BECC4A1008A7F35 /* AppDelegate.m */, + 3EEA4EED1BECC4A1008A7F35 /* ViewController.h */, + 3EEA4EEE1BECC4A1008A7F35 /* ViewController.m */, + 3EEA4EF31BECC4A1008A7F35 /* Assets.xcassets */, + 3EEA4EF81BECC4A1008A7F35 /* Info.plist */, + 3EEA4EE71BECC4A1008A7F35 /* Supporting Files */, + 3EEA4F041BECC6C9008A7F35 /* Post.h */, + 3EEA4F051BECC6C9008A7F35 /* Post.m */, + 3EEA4F071BECC855008A7F35 /* PostNode.h */, + 3EEA4F081BECC855008A7F35 /* PostNode.m */, + 3EEA4F0A1BECCA0A008A7F35 /* TextStyles.h */, + 3EEA4F0B1BECCA0A008A7F35 /* TextStyles.m */, + 3EEA4F1B1BECE358008A7F35 /* LikesNode.h */, + 3EEA4F1C1BECE358008A7F35 /* LikesNode.m */, + 3EEA4F361BECE775008A7F35 /* CommentsNode.h */, + 3EEA4F371BECE775008A7F35 /* CommentsNode.m */, + ); + path = Sample; + sourceTree = ""; + }; + 3EEA4EE71BECC4A1008A7F35 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 3EEA4EFE1BECC4E8008A7F35 /* Default-568h@2x.png */, + 3EEA4EFF1BECC4E8008A7F35 /* Default-667h@2x.png */, + 3EEA4F001BECC4E8008A7F35 /* Default-736h@3x.png */, + 3EEA4EE81BECC4A1008A7F35 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 3EEA4F0D1BECDCA6008A7F35 /* Images */ = { + isa = PBXGroup; + children = ( + 3EEA4F391BECE99F008A7F35 /* icon_more.png */, + 3EEA4F3A1BECE99F008A7F35 /* icon_more@2x.png */, + 3EEA4F3B1BECE99F008A7F35 /* icon_more@3x.png */, + 3EEA4F1E1BECE440008A7F35 /* icon_liked.png */, + 3EEA4F1F1BECE440008A7F35 /* icon_liked@2x.png */, + 3EEA4F201BECE440008A7F35 /* icon_liked@3x.png */, + 3EEA4F241BECE440008A7F35 /* icon_like@3x.png */, + 3EEA4F251BECE440008A7F35 /* icon_like@2x.png */, + 3EEA4F261BECE440008A7F35 /* icon_like.png */, + 3EEA4F271BECE440008A7F35 /* icon_comment@3x.png */, + 3EEA4F281BECE440008A7F35 /* icon_comment@2x.png */, + 3EEA4F291BECE440008A7F35 /* icon_comment.png */, + 3EEA4F0E1BECDCD6008A7F35 /* icon_android.png */, + 3EEA4F0F1BECDCD6008A7F35 /* icon_android@2x.png */, + 3EEA4F101BECDCD6008A7F35 /* icon_android@3x.png */, + 3EEA4F111BECDCD6008A7F35 /* icon_ios.png */, + 3EEA4F121BECDCD6008A7F35 /* icon_ios@2x.png */, + 3EEA4F131BECDCD6008A7F35 /* icon_ios@3x.png */, + ); + name = Images; + sourceTree = ""; + }; + 842ADAFE88475D19B24183AC /* Pods */ = { + isa = PBXGroup; + children = ( + CC6F2ABE8383FAB21802C734 /* Pods-Sample.debug.xcconfig */, + FCCC1AD413FCA8603156ED15 /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + EED34FA6D8171DF44757C852 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 193CE60FE6429EFEBF6EA52B /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 3EEA4EE31BECC4A1008A7F35 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3EEA4EFB1BECC4A1008A7F35 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + B5BD9E5609B2CB179EEE0CF4 /* Check Pods Manifest.lock */, + 3EEA4EE01BECC4A1008A7F35 /* Sources */, + 3EEA4EE11BECC4A1008A7F35 /* Frameworks */, + 3EEA4EE21BECC4A1008A7F35 /* Resources */, + 21F2C1D9B53F9468EAF1653F /* Copy Pods Resources */, + 852437589F1D53B9483A75DF /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 3EEA4EE41BECC4A1008A7F35 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 3EEA4EDC1BECC4A1008A7F35 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0710; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 3EEA4EE31BECC4A1008A7F35 = { + CreatedOnToolsVersion = 7.1; + }; + }; + }; + buildConfigurationList = 3EEA4EDF1BECC4A1008A7F35 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 3EEA4EDB1BECC4A1008A7F35; + productRefGroup = 3EEA4EE51BECC4A1008A7F35 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 3EEA4EE31BECC4A1008A7F35 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 3EEA4EE21BECC4A1008A7F35 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3EEA4F2A1BECE440008A7F35 /* icon_liked.png in Resources */, + 3EEA4F181BECDCD6008A7F35 /* icon_ios@2x.png in Resources */, + 3EEA4F311BECE440008A7F35 /* icon_like@2x.png in Resources */, + 3EEA4F3D1BECE99F008A7F35 /* icon_more@2x.png in Resources */, + 3EEA4F141BECDCD6008A7F35 /* icon_android.png in Resources */, + 3EEA4F3E1BECE99F008A7F35 /* icon_more@3x.png in Resources */, + 3EEA4F2B1BECE440008A7F35 /* icon_liked@2x.png in Resources */, + 3EEA4F351BECE440008A7F35 /* icon_comment.png in Resources */, + 3EEA4EF41BECC4A1008A7F35 /* Assets.xcassets in Resources */, + 3EEA4F171BECDCD6008A7F35 /* icon_ios.png in Resources */, + 3EEA4F021BECC4E8008A7F35 /* Default-667h@2x.png in Resources */, + 3EEA4F161BECDCD6008A7F35 /* icon_android@3x.png in Resources */, + 3EEA4F191BECDCD6008A7F35 /* icon_ios@3x.png in Resources */, + 3EEA4F331BECE440008A7F35 /* icon_comment@3x.png in Resources */, + 3EEA4F341BECE440008A7F35 /* icon_comment@2x.png in Resources */, + 3EEA4F321BECE440008A7F35 /* icon_like.png in Resources */, + 3EEA4F151BECDCD6008A7F35 /* icon_android@2x.png in Resources */, + 3EEA4F301BECE440008A7F35 /* icon_like@3x.png in Resources */, + 3EEA4F2C1BECE440008A7F35 /* icon_liked@3x.png in Resources */, + 3EEA4F011BECC4E8008A7F35 /* Default-568h@2x.png in Resources */, + 3EEA4F031BECC4E8008A7F35 /* Default-736h@3x.png in Resources */, + 3EEA4F3C1BECE99F008A7F35 /* icon_more.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 21F2C1D9B53F9468EAF1653F /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 852437589F1D53B9483A75DF /* 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-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B5BD9E5609B2CB179EEE0CF4 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3EEA4EE01BECC4A1008A7F35 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3EEA4EEF1BECC4A1008A7F35 /* ViewController.m in Sources */, + 3EEA4EEC1BECC4A1008A7F35 /* AppDelegate.m in Sources */, + 3EEA4EE91BECC4A1008A7F35 /* main.m in Sources */, + 3EEA4F061BECC6C9008A7F35 /* Post.m in Sources */, + 3EEA4F0C1BECCA0A008A7F35 /* TextStyles.m in Sources */, + 3EEA4F381BECE775008A7F35 /* CommentsNode.m in Sources */, + 3EEA4F091BECC855008A7F35 /* PostNode.m in Sources */, + 3EEA4F1D1BECE358008A7F35 /* LikesNode.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 3EEA4EF91BECC4A1008A7F35 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 3EEA4EFA1BECC4A1008A7F35 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 3EEA4EFC1BECC4A1008A7F35 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CC6F2ABE8383FAB21802C734 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3EEA4EFD1BECC4A1008A7F35 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FCCC1AD413FCA8603156ED15 /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3EEA4EDF1BECC4A1008A7F35 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3EEA4EF91BECC4A1008A7F35 /* Debug */, + 3EEA4EFA1BECC4A1008A7F35 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3EEA4EFB1BECC4A1008A7F35 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3EEA4EFC1BECC4A1008A7F35 /* Debug */, + 3EEA4EFD1BECC4A1008A7F35 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 3EEA4EDC1BECC4A1008A7F35 /* Project object */; +} diff --git a/examples/SocialAppLayout/Sample/PostNode.m b/examples/SocialAppLayout/Sample/PostNode.m index 3206b00d99..d1b4a9cba1 100644 --- a/examples/SocialAppLayout/Sample/PostNode.m +++ b/examples/SocialAppLayout/Sample/PostNode.m @@ -50,6 +50,8 @@ - (instancetype)initWithPost:(Post *)post if (self) { _post = post; + self.selectionStyle = UITableViewCellSelectionStyleNone; + // Name node _nameNode = [[ASTextNode alloc] init]; _nameNode.attributedText = [[NSAttributedString alloc] initWithString:_post.name attributes:[TextStyles nameStyle]]; @@ -180,6 +182,10 @@ - (instancetype)initWithPost:(Post *)post _optionsNode = [[ASImageNode alloc] init]; _optionsNode.image = [UIImage imageNamed:@"icon_more"]; [self addSubnode:_optionsNode]; + + for (ASDisplayNode *node in self.subnodes) { + node.layerBacked = YES; + } } return self; } @@ -212,16 +218,8 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; spacer.style.flexGrow = 1.0; - // NOTE: This inset is not actually required by the layout, but is an example of the upward propogation of layoutable - // properties. Specifically, .flexGrow from the child is transferred to the inset spec so they can expand together. - // Without this capability, it would be required to set insetSpacer.flexGrow = 1.0; - ASInsetLayoutSpec *insetSpacer = - [ASInsetLayoutSpec - insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 0, 0, 0) - child:spacer]; - // Horizontal stack for name, username, via icon and time - NSMutableArray *layoutSpecChildren = [@[_nameNode, _usernameNode, insetSpacer] mutableCopy]; + NSMutableArray *layoutSpecChildren = [@[_nameNode, _usernameNode, spacer] mutableCopy]; if (_post.via != 0) { [layoutSpecChildren addObject:_viaNode]; } @@ -258,10 +256,9 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize // Only add the media node if an image is present if (_mediaNode.image != nil) { - CGFloat imageRatio = (_mediaNode.image != nil ? _mediaNode.image.size.height / _mediaNode.image.size.width : 0.5); ASRatioLayoutSpec *imagePlace = [ASRatioLayoutSpec - ratioLayoutSpecWithRatio:imageRatio + ratioLayoutSpecWithRatio:0.5 child:_mediaNode]; imagePlace.style.spacingAfter = 3.0; imagePlace.style.spacingBefore = 3.0; diff --git a/examples/SocialAppLayout/Sample/ViewController.m b/examples/SocialAppLayout/Sample/ViewController.m index 71458a1ae8..28bdf90283 100644 --- a/examples/SocialAppLayout/Sample/ViewController.m +++ b/examples/SocialAppLayout/Sample/ViewController.m @@ -133,18 +133,4 @@ - (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger return self.socialAppDataSource.count; } -- (void)tableNode:(ASTableNode *)tableNode didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - PostNode *postNode = (PostNode *)[_tableNode nodeForRowAtIndexPath:indexPath]; - Post *post = self.socialAppDataSource[indexPath.row]; - - BOOL shouldRasterize = postNode.shouldRasterizeDescendants; - shouldRasterize = !shouldRasterize; - postNode.shouldRasterizeDescendants = shouldRasterize; - - NSLog(@"%@ rasterization for %@'s post: %@", shouldRasterize ? @"Enabling" : @"Disabling", post.name, postNode); - - [tableNode deselectRowAtIndexPath:indexPath animated:YES]; -} - @end diff --git a/examples/Swift/Podfile b/examples/Swift/Podfile index 628b25d36c..4013adc2f2 100644 --- a/examples/Swift/Podfile +++ b/examples/Swift/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' use_frameworks! diff --git a/examples/Swift/Sample/TailLoadingCellNode.swift b/examples/Swift/Sample/TailLoadingCellNode.swift index 4e995443dc..b3a0bff6f2 100644 --- a/examples/Swift/Sample/TailLoadingCellNode.swift +++ b/examples/Swift/Sample/TailLoadingCellNode.swift @@ -55,7 +55,10 @@ final class SpinnerNode: ASDisplayNode { } override init() { - super.init(viewBlock: { UIActivityIndicatorView(activityIndicatorStyle: .Gray) }, didLoadBlock: nil) + super.init() + setViewBlock { + UIActivityIndicatorView(activityIndicatorStyle: .Gray) + } // Set spinner node to default size of the activitiy indicator view self.style.preferredSize = CGSizeMake(20.0, 20.0) diff --git a/examples/VerticalWithinHorizontalScrolling/Podfile b/examples/VerticalWithinHorizontalScrolling/Podfile index 919de4b311..defaf55058 100644 --- a/examples/VerticalWithinHorizontalScrolling/Podfile +++ b/examples/VerticalWithinHorizontalScrolling/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj b/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj index 0dec823b1e..7f7950060a 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj +++ b/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj @@ -1,806 +1,375 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 05561CFB19D4F94A00CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - GradientTableNode.h - sourceTree - <group> - - 05561CFC19D4F94A00CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.cpp.objcpp - path - GradientTableNode.mm - sourceTree - <group> - - 05561CFD19D4F94A00CBA93C - - fileRef - 05561CFC19D4F94A00CBA93C - isa - PBXBuildFile - - 0585427F19D4DBE100606EA6 - - isa - PBXFileReference - lastKnownFileType - image.png - name - Default-568h@2x.png - path - ../Default-568h@2x.png - sourceTree - <group> - - 0585428019D4DBE100606EA6 - - fileRef - 0585427F19D4DBE100606EA6 - isa - PBXBuildFile - - 05E2127819D4DB510098F589 - - children - - 05E2128319D4DB510098F589 - 05E2128219D4DB510098F589 - 1A943BF0259746F18D6E423F - 1AE410B73DA5C3BD087ACDD7 - - indentWidth - 2 - isa - PBXGroup - sourceTree - <group> - tabWidth - 2 - usesTabs - 0 - - 05E2127919D4DB510098F589 - - attributes - - LastUpgradeCheck - 0600 - ORGANIZATIONNAME - Facebook - TargetAttributes - - 05E2128019D4DB510098F589 - - CreatedOnToolsVersion - 6.0.1 - - - - buildConfigurationList - 05E2127C19D4DB510098F589 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - 05E2127819D4DB510098F589 - productRefGroup - 05E2128219D4DB510098F589 - projectDirPath - - projectReferences - - projectRoot - - targets - - 05E2128019D4DB510098F589 - - - 05E2127C19D4DB510098F589 - - buildConfigurations - - 05E212A219D4DB510098F589 - 05E212A319D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E2127D19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 18C2ED861B9B8CE700F627B3 - 05561CFD19D4F94A00CBA93C - 05E2128D19D4DB510098F589 - 05E2128A19D4DB510098F589 - 05E2128719D4DB510098F589 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127E19D4DB510098F589 - - buildActionMask - 2147483647 - files - - C81806AD7AEA1CC3061C4742 - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127F19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 0585428019D4DBE100606EA6 - 6C2C82AC19EE274300767484 - 6C2C82AD19EE274300767484 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2128019D4DB510098F589 - - buildConfigurationList - 05E212A419D4DB510098F589 - buildPhases - - E080B80F89C34A25B3488E26 - 05E2127D19D4DB510098F589 - 05E2127E19D4DB510098F589 - 05E2127F19D4DB510098F589 - F012A6F39E0149F18F564F50 - 6E94068CFAA7736333E7D960 - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - 05E2128119D4DB510098F589 - productType - com.apple.product-type.application - - 05E2128119D4DB510098F589 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - 05E2128219D4DB510098F589 - - children - - 05E2128119D4DB510098F589 - - isa - PBXGroup - name - Products - sourceTree - <group> - - 05E2128319D4DB510098F589 - - children - - 05E2128819D4DB510098F589 - 05E2128919D4DB510098F589 - 05E2128B19D4DB510098F589 - 05E2128C19D4DB510098F589 - 05561CFB19D4F94A00CBA93C - 05561CFC19D4F94A00CBA93C - 18C2ED841B9B8CE700F627B3 - 18C2ED851B9B8CE700F627B3 - 05E2128419D4DB510098F589 - - isa - PBXGroup - path - Sample - sourceTree - <group> - - 05E2128419D4DB510098F589 - - children - - 0585427F19D4DBE100606EA6 - 6C2C82AA19EE274300767484 - 6C2C82AB19EE274300767484 - 05E2128519D4DB510098F589 - 05E2128619D4DB510098F589 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - 05E2128519D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - 05E2128619D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - 05E2128719D4DB510098F589 - - fileRef - 05E2128619D4DB510098F589 - isa - PBXBuildFile - - 05E2128819D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - 05E2128919D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - 05E2128A19D4DB510098F589 - - fileRef - 05E2128919D4DB510098F589 - isa - PBXBuildFile - - 05E2128B19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - 05E2128C19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - 05E2128D19D4DB510098F589 - - fileRef - 05E2128C19D4DB510098F589 - isa - PBXBuildFile - - 05E212A219D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_SYMBOLS_PRIVATE_EXTERN - NO - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - - isa - XCBuildConfiguration - name - Debug - - 05E212A319D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - YES - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - 05E212A419D4DB510098F589 - - buildConfigurations - - 05E212A519D4DB510098F589 - 05E212A619D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E212A519D4DB510098F589 - - baseConfigurationReference - 19760F30C80D89FC055CF57A - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Debug - - 05E212A619D4DB510098F589 - - baseConfigurationReference - A5A1F5A0D2B4375F57D02F1A - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Release - - 18C2ED841B9B8CE700F627B3 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - RandomCoreGraphicsNode.h - sourceTree - <group> - - 18C2ED851B9B8CE700F627B3 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - RandomCoreGraphicsNode.m - sourceTree - <group> - - 18C2ED861B9B8CE700F627B3 - - fileRef - 18C2ED851B9B8CE700F627B3 - isa - PBXBuildFile - - 19760F30C80D89FC055CF57A - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - 1A943BF0259746F18D6E423F - - children - - DBF6047FF8AB82FD183F47C3 - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - 1AE410B73DA5C3BD087ACDD7 - - children - - 19760F30C80D89FC055CF57A - A5A1F5A0D2B4375F57D02F1A - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 6C2C82AA19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-667h@2x.png - sourceTree - SOURCE_ROOT - - 6C2C82AB19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-736h@3x.png - sourceTree - SOURCE_ROOT - - 6C2C82AC19EE274300767484 - - fileRef - 6C2C82AA19EE274300767484 - isa - PBXBuildFile - - 6C2C82AD19EE274300767484 - - fileRef - 6C2C82AB19EE274300767484 - isa - PBXBuildFile - - 6E94068CFAA7736333E7D960 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - A5A1F5A0D2B4375F57D02F1A - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - C81806AD7AEA1CC3061C4742 - - fileRef - DBF6047FF8AB82FD183F47C3 - isa - PBXBuildFile - - DBF6047FF8AB82FD183F47C3 - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - E080B80F89C34A25B3488E26 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - F012A6F39E0149F18F564F50 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - - rootObject - 05E2127919D4DB510098F589 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 05561CFD19D4F94A00CBA93C /* GradientTableNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05561CFC19D4F94A00CBA93C /* GradientTableNode.mm */; }; + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; + C81806AD7AEA1CC3061C4742 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DBF6047FF8AB82FD183F47C3 /* libPods-Sample.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 05561CFB19D4F94A00CBA93C /* GradientTableNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GradientTableNode.h; sourceTree = ""; }; + 05561CFC19D4F94A00CBA93C /* GradientTableNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GradientTableNode.mm; sourceTree = ""; }; + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RandomCoreGraphicsNode.h; sourceTree = ""; }; + 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RandomCoreGraphicsNode.m; sourceTree = ""; }; + 19760F30C80D89FC055CF57A /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + A5A1F5A0D2B4375F57D02F1A /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + DBF6047FF8AB82FD183F47C3 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C81806AD7AEA1CC3061C4742 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + 05561CFB19D4F94A00CBA93C /* GradientTableNode.h */, + 05561CFC19D4F94A00CBA93C /* GradientTableNode.mm */, + 18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */, + 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + DBF6047FF8AB82FD183F47C3 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + 19760F30C80D89FC055CF57A /* Pods-Sample.debug.xcconfig */, + A5A1F5A0D2B4375F57D02F1A /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + 6E94068CFAA7736333E7D960 /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0600; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 6E94068CFAA7736333E7D960 /* 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-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */, + 05561CFD19D4F94A00CBA93C /* GradientTableNode.mm in Sources */, + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 19760F30C80D89FC055CF57A /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A5A1F5A0D2B4375F57D02F1A /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m index 0e8be6ed5a..4791a42f08 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m @@ -39,7 +39,7 @@ - (instancetype)init _pagerNode = [[ASPagerNode alloc] init]; _pagerNode.dataSource = self; _pagerNode.delegate = self; - [ASRangeController setShouldShowRangeDebugOverlay:YES]; + ASDisplayNode.shouldShowRangeDebugOverlay = YES; // Could implement ASCollectionDelegate if we wanted extra callbacks, like from UIScrollView. //_pagerNode.delegate = self; @@ -77,11 +77,15 @@ - (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index CGSize boundsSize = pagerNode.bounds.size; CGSize gradientRowSize = CGSizeMake(boundsSize.width, 100); GradientTableNode *node = [[GradientTableNode alloc] initWithElementSize:gradientRowSize]; - node.style.preferredSize = boundsSize; node.pageNumber = index; return node; } +- (ASSizeRange)pagerNode:(ASPagerNode *)pagerNode constrainedSizeForNodeAtIndex:(NSInteger)index; +{ + return ASSizeRangeMake(pagerNode.bounds.size); +} + - (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode { return 10; diff --git a/examples/Videos/Podfile b/examples/Videos/Podfile index 919de4b311..defaf55058 100644 --- a/examples/Videos/Podfile +++ b/examples/Videos/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples/Videos/Sample.xcworkspace/contents.xcworkspacedata b/examples/Videos/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7b5a2f3050..0000000000 --- a/examples/Videos/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/Videos/Sample/ViewController.m b/examples/Videos/Sample/ViewController.m index 6b2bbc33fb..b0de8101b3 100644 --- a/examples/Videos/Sample/ViewController.m +++ b/examples/Videos/Sample/ViewController.m @@ -147,8 +147,8 @@ - (ASButtonNode *)playButton; ASButtonNode *playButtonNode = [[ASButtonNode alloc] init]; UIImage *image = [UIImage imageNamed:@"playButton@2x.png"]; - [playButtonNode setImage:image forState:ASControlStateNormal]; - [playButtonNode setImage:[UIImage imageNamed:@"playButtonSelected@2x.png"] forState:ASControlStateHighlighted]; + [playButtonNode setImage:image forState:UIControlStateNormal]; + [playButtonNode setImage:[UIImage imageNamed:@"playButtonSelected@2x.png"] forState:UIControlStateHighlighted]; // Change placement of play button if necessary //playButtonNode.contentHorizontalAlignment = ASHorizontalAlignmentStart; diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.pbxproj b/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..5927a1d960 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.pbxproj @@ -0,0 +1,523 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 3A2362FB1E2D33A0007E08F1 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2362FA1E2D33A0007E08F1 /* Date.swift */; }; + 3A7A28D91E2F7410003E2B8D /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7A28D81E2F7410003E2B8D /* UIImage.swift */; }; + 3AB33F5E1E1F94530039F711 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F5D1E1F94530039F711 /* AppDelegate.swift */; }; + 3AB33F651E1F94530039F711 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AB33F641E1F94530039F711 /* Assets.xcassets */; }; + 3AB33F681E1F94530039F711 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3AB33F661E1F94530039F711 /* LaunchScreen.storyboard */; }; + 3AB33F761E1F9C330039F711 /* PhotoFeedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F751E1F9C330039F711 /* PhotoFeedTableViewController.swift */; }; + 3AB33F781E1F9C400039F711 /* PhotoFeedTableNodeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F771E1F9C400039F711 /* PhotoFeedTableNodeController.swift */; }; + 3AB33F7B1E1F9E630039F711 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F7A1E1F9E630039F711 /* UIColor.swift */; }; + 3AB33F811E1FDE100039F711 /* Webservice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F801E1FDE100039F711 /* Webservice.swift */; }; + 3AB33F831E20E81E0039F711 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F821E20E81E0039F711 /* Constants.swift */; }; + 3AB33F861E20E9B10039F711 /* PhotoFeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F851E20E9B10039F711 /* PhotoFeedModel.swift */; }; + 3AB33F881E20ED460039F711 /* PhotoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F871E20ED460039F711 /* PhotoModel.swift */; }; + 3AB33F8C1E2106F30039F711 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F8B1E2106F30039F711 /* URL.swift */; }; + 3AB33F961E2269D40039F711 /* PopularPageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F951E2269D40039F711 /* PopularPageModel.swift */; }; + 3AB33F981E22A0080039F711 /* PX500Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F971E22A0080039F711 /* PX500Convenience.swift */; }; + 3AB33F9E1E22D9DB0039F711 /* PhotoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F9D1E22D9DB0039F711 /* PhotoTableViewCell.swift */; }; + 3AB33FA21E230A160039F711 /* NetworkImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33FA11E230A160039F711 /* NetworkImageView.swift */; }; + 3AB33FA41E2337850039F711 /* PhotoTableNodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33FA31E2337850039F711 /* PhotoTableNodeCell.swift */; }; + 7E438240D2C4026931D60594 /* Pods_ASDKgram_Swift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D7D664E4FF432C4AE232A56 /* Pods_ASDKgram_Swift.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 019E984FADA258377FC6B2D8 /* Pods-ASDKgram-Swift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ASDKgram-Swift.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ASDKgram-Swift/Pods-ASDKgram-Swift.debug.xcconfig"; sourceTree = ""; }; + 3A2362FA1E2D33A0007E08F1 /* Date.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; + 3A7A28D81E2F7410003E2B8D /* UIImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; + 3AB33F5A1E1F94520039F711 /* ASDKgram-Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ASDKgram-Swift.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AB33F5D1E1F94530039F711 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 3AB33F641E1F94530039F711 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 3AB33F671E1F94530039F711 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 3AB33F691E1F94530039F711 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3AB33F751E1F9C330039F711 /* PhotoFeedTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoFeedTableViewController.swift; sourceTree = ""; }; + 3AB33F771E1F9C400039F711 /* PhotoFeedTableNodeController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoFeedTableNodeController.swift; sourceTree = ""; }; + 3AB33F7A1E1F9E630039F711 /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; + 3AB33F801E1FDE100039F711 /* Webservice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Webservice.swift; sourceTree = ""; }; + 3AB33F821E20E81E0039F711 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 3AB33F851E20E9B10039F711 /* PhotoFeedModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoFeedModel.swift; sourceTree = ""; }; + 3AB33F871E20ED460039F711 /* PhotoModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoModel.swift; sourceTree = ""; }; + 3AB33F8B1E2106F30039F711 /* URL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; + 3AB33F951E2269D40039F711 /* PopularPageModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopularPageModel.swift; sourceTree = ""; }; + 3AB33F971E22A0080039F711 /* PX500Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PX500Convenience.swift; sourceTree = ""; }; + 3AB33F9D1E22D9DB0039F711 /* PhotoTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoTableViewCell.swift; sourceTree = ""; }; + 3AB33FA11E230A160039F711 /* NetworkImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkImageView.swift; sourceTree = ""; }; + 3AB33FA31E2337850039F711 /* PhotoTableNodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoTableNodeCell.swift; sourceTree = ""; }; + 4D7D664E4FF432C4AE232A56 /* Pods_ASDKgram_Swift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ASDKgram_Swift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A3A86E74A8C3F06D7688AACB /* Pods-ASDKgram-Swift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ASDKgram-Swift.release.xcconfig"; path = "Pods/Target Support Files/Pods-ASDKgram-Swift/Pods-ASDKgram-Swift.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3AB33F571E1F94520039F711 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E438240D2C4026931D60594 /* Pods_ASDKgram_Swift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3AB33F511E1F94520039F711 = { + isa = PBXGroup; + children = ( + 3AB33F5C1E1F94530039F711 /* ASDKgram-Swift */, + 3AB33F5B1E1F94520039F711 /* Products */, + 78A64CA59A49BE1637214DF1 /* Pods */, + A7DD645D70CF34C7CA3B1A8B /* Frameworks */, + ); + sourceTree = ""; + }; + 3AB33F5B1E1F94520039F711 /* Products */ = { + isa = PBXGroup; + children = ( + 3AB33F5A1E1F94520039F711 /* ASDKgram-Swift.app */, + ); + name = Products; + sourceTree = ""; + }; + 3AB33F5C1E1F94530039F711 /* ASDKgram-Swift */ = { + isa = PBXGroup; + children = ( + 3AB33F991E22CF160039F711 /* Views */, + 3AB33F841E20E98C0039F711 /* Model */, + 3AB33F7D1E1FDA890039F711 /* Client */, + 3AB33F791E1F9E4E0039F711 /* Extensions */, + 3AB33F721E1F9B650039F711 /* Controllers */, + 3AB33F5D1E1F94530039F711 /* AppDelegate.swift */, + 3AB33F821E20E81E0039F711 /* Constants.swift */, + 3AB33F641E1F94530039F711 /* Assets.xcassets */, + 3AB33F661E1F94530039F711 /* LaunchScreen.storyboard */, + 3AB33F691E1F94530039F711 /* Info.plist */, + ); + path = "ASDKgram-Swift"; + sourceTree = ""; + }; + 3AB33F721E1F9B650039F711 /* Controllers */ = { + isa = PBXGroup; + children = ( + 3AB33F741E1F9B9F0039F711 /* ASDK */, + 3AB33F731E1F9B950039F711 /* UIKit */, + ); + name = Controllers; + sourceTree = ""; + }; + 3AB33F731E1F9B950039F711 /* UIKit */ = { + isa = PBXGroup; + children = ( + 3AB33F751E1F9C330039F711 /* PhotoFeedTableViewController.swift */, + ); + name = UIKit; + sourceTree = ""; + }; + 3AB33F741E1F9B9F0039F711 /* ASDK */ = { + isa = PBXGroup; + children = ( + 3AB33F771E1F9C400039F711 /* PhotoFeedTableNodeController.swift */, + ); + name = ASDK; + sourceTree = ""; + }; + 3AB33F791E1F9E4E0039F711 /* Extensions */ = { + isa = PBXGroup; + children = ( + 3AB33F7A1E1F9E630039F711 /* UIColor.swift */, + 3AB33F8B1E2106F30039F711 /* URL.swift */, + 3A2362FA1E2D33A0007E08F1 /* Date.swift */, + 3A7A28D81E2F7410003E2B8D /* UIImage.swift */, + ); + name = Extensions; + sourceTree = ""; + }; + 3AB33F7D1E1FDA890039F711 /* Client */ = { + isa = PBXGroup; + children = ( + 3AB33F801E1FDE100039F711 /* Webservice.swift */, + 3AB33F971E22A0080039F711 /* PX500Convenience.swift */, + ); + name = Client; + sourceTree = ""; + }; + 3AB33F841E20E98C0039F711 /* Model */ = { + isa = PBXGroup; + children = ( + 3AB33F851E20E9B10039F711 /* PhotoFeedModel.swift */, + 3AB33F871E20ED460039F711 /* PhotoModel.swift */, + 3AB33F951E2269D40039F711 /* PopularPageModel.swift */, + ); + name = Model; + sourceTree = ""; + }; + 3AB33F991E22CF160039F711 /* Views */ = { + isa = PBXGroup; + children = ( + 3AB33F9B1E22CF3C0039F711 /* UIKit */, + 3AB33F9C1E22CF5C0039F711 /* ASDK */, + ); + name = Views; + sourceTree = ""; + }; + 3AB33F9B1E22CF3C0039F711 /* UIKit */ = { + isa = PBXGroup; + children = ( + 3AB33F9D1E22D9DB0039F711 /* PhotoTableViewCell.swift */, + 3AB33FA11E230A160039F711 /* NetworkImageView.swift */, + ); + name = UIKit; + sourceTree = ""; + }; + 3AB33F9C1E22CF5C0039F711 /* ASDK */ = { + isa = PBXGroup; + children = ( + 3AB33FA31E2337850039F711 /* PhotoTableNodeCell.swift */, + ); + name = ASDK; + sourceTree = ""; + }; + 78A64CA59A49BE1637214DF1 /* Pods */ = { + isa = PBXGroup; + children = ( + 019E984FADA258377FC6B2D8 /* Pods-ASDKgram-Swift.debug.xcconfig */, + A3A86E74A8C3F06D7688AACB /* Pods-ASDKgram-Swift.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + A7DD645D70CF34C7CA3B1A8B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4D7D664E4FF432C4AE232A56 /* Pods_ASDKgram_Swift.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 3AB33F591E1F94520039F711 /* ASDKgram-Swift */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3AB33F6C1E1F94530039F711 /* Build configuration list for PBXNativeTarget "ASDKgram-Swift" */; + buildPhases = ( + A5A729883237749EE5D2DB1C /* [CP] Check Pods Manifest.lock */, + 3AB33F561E1F94520039F711 /* Sources */, + 3AB33F571E1F94520039F711 /* Frameworks */, + 3AB33F581E1F94520039F711 /* Resources */, + 154783123A953C3AFB9805CF /* [CP] Embed Pods Frameworks */, + 07D25AC7E9C9518F14F0C929 /* [CP] Copy Pods Resources */, + 3A7BEDD71E254278005769D4 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ASDKgram-Swift"; + productName = "ASDKgram-Swift"; + productReference = 3AB33F5A1E1F94520039F711 /* ASDKgram-Swift.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 3AB33F521E1F94520039F711 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0820; + LastUpgradeCheck = 0820; + ORGANIZATIONNAME = "Calum Harris"; + TargetAttributes = { + 3AB33F591E1F94520039F711 = { + CreatedOnToolsVersion = 8.2; + DevelopmentTeam = B3H446T9U7; + LastSwiftMigration = 0820; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 3AB33F551E1F94520039F711 /* Build configuration list for PBXProject "ASDKgram-Swift" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 3AB33F511E1F94520039F711; + productRefGroup = 3AB33F5B1E1F94520039F711 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 3AB33F591E1F94520039F711 /* ASDKgram-Swift */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 3AB33F581E1F94520039F711 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3AB33F681E1F94530039F711 /* LaunchScreen.storyboard in Resources */, + 3AB33F651E1F94530039F711 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 07D25AC7E9C9518F14F0C929 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ASDKgram-Swift/Pods-ASDKgram-Swift-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 154783123A953C3AFB9805CF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ASDKgram-Swift/Pods-ASDKgram-Swift-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3A7BEDD71E254278005769D4 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = ""; + }; + A5A729883237749EE5D2DB1C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3AB33F561E1F94520039F711 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3AB33F781E1F9C400039F711 /* PhotoFeedTableNodeController.swift in Sources */, + 3A2362FB1E2D33A0007E08F1 /* Date.swift in Sources */, + 3AB33F7B1E1F9E630039F711 /* UIColor.swift in Sources */, + 3AB33F981E22A0080039F711 /* PX500Convenience.swift in Sources */, + 3AB33FA41E2337850039F711 /* PhotoTableNodeCell.swift in Sources */, + 3AB33FA21E230A160039F711 /* NetworkImageView.swift in Sources */, + 3AB33F8C1E2106F30039F711 /* URL.swift in Sources */, + 3AB33F831E20E81E0039F711 /* Constants.swift in Sources */, + 3AB33F961E2269D40039F711 /* PopularPageModel.swift in Sources */, + 3A7A28D91E2F7410003E2B8D /* UIImage.swift in Sources */, + 3AB33F5E1E1F94530039F711 /* AppDelegate.swift in Sources */, + 3AB33F811E1FDE100039F711 /* Webservice.swift in Sources */, + 3AB33F9E1E22D9DB0039F711 /* PhotoTableViewCell.swift in Sources */, + 3AB33F861E20E9B10039F711 /* PhotoFeedModel.swift in Sources */, + 3AB33F881E20ED460039F711 /* PhotoModel.swift in Sources */, + 3AB33F761E1F9C330039F711 /* PhotoFeedTableViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 3AB33F661E1F94530039F711 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 3AB33F671E1F94530039F711 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 3AB33F6A1E1F94530039F711 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3AB33F6B1E1F94530039F711 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 3AB33F6D1E1F94530039F711 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 019E984FADA258377FC6B2D8 /* Pods-ASDKgram-Swift.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = B3H446T9U7; + INFOPLIST_FILE = "ASDKgram-Swift/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.RenaldoMoon.ASDKgram-Swift"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 3AB33F6E1E1F94530039F711 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3A86E74A8C3F06D7688AACB /* Pods-ASDKgram-Swift.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = B3H446T9U7; + INFOPLIST_FILE = "ASDKgram-Swift/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.RenaldoMoon.ASDKgram-Swift"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3AB33F551E1F94520039F711 /* Build configuration list for PBXProject "ASDKgram-Swift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3AB33F6A1E1F94530039F711 /* Debug */, + 3AB33F6B1E1F94530039F711 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3AB33F6C1E1F94530039F711 /* Build configuration list for PBXNativeTarget "ASDKgram-Swift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3AB33F6D1E1F94530039F711 /* Debug */, + 3AB33F6E1E1F94530039F711 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 3AB33F521E1F94520039F711 /* Project object */; +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..96a0fb6dec --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/AppDelegate.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/AppDelegate.swift new file mode 100644 index 0000000000..886d28ca70 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/AppDelegate.swift @@ -0,0 +1,61 @@ +// +// AppDelegate.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 06/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit +import AsyncDisplayKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + + // UIKit Home Feed viewController & navController + + let UIKitNavController = UINavigationController(rootViewController: PhotoFeedTableViewController()) + UIKitNavController.tabBarItem.title = "UIKit" + + // ASDK Home Feed viewController & navController + + let ASDKNavController = UINavigationController(rootViewController: PhotoFeedTableNodeController()) + ASDKNavController.tabBarItem.title = "ASDK" + + // UITabBarController + + let tabBarController = UITabBarController() + tabBarController.viewControllers = [UIKitNavController, ASDKNavController] + tabBarController.selectedIndex = 1 + tabBarController.tabBar.tintColor = UIColor.mainBarTintColor() + + // Nav Bar appearance + + UINavigationBar.appearance().barTintColor = UIColor.mainBarTintColor() + + // UIWindow + + window = UIWindow() + window?.backgroundColor = .white + window?.rootViewController = tabBarController + window?.makeKeyAndVisible() + + return true + } + +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..1d060ed288 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,93 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/LaunchScreen.storyboard b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..c1191aaf8d --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/Main.storyboard b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..273375fc70 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Constants.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Constants.swift new file mode 100644 index 0000000000..f85bb817fe --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Constants.swift @@ -0,0 +1,45 @@ +// +// Constants +// ASDKgram-Swift +// +// Created by Calum Harris on 07/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// swiftlint:disable nesting + +import UIKit + +struct Constants { + + struct PX500 { + struct URLS { + static let Host = "https://api.500px.com/v1/" + static let PopularEndpoint = "photos?feature=popular&exclude=Nude,People,Fashion&sort=rating&image_size=3&include_store=store_download&include_states=voted" + static let SearchEndpoint = "photos/search?geo=" //latitude,longitude,radius + static let UserEndpoint = "photos?user_id=" + static let ConsumerKey = "&consumer_key=Fi13GVb8g53sGvHICzlram7QkKOlSDmAmp9s9aqC" + } + } + + struct CellLayout { + static let FontSize: CGFloat = 14 + static let HeaderHeight: CGFloat = 50 + static let UserImageHeight: CGFloat = 30 + static let HorizontalBuffer: CGFloat = 10 + static let VerticalBuffer: CGFloat = 5 + static let InsetForAvatar = UIEdgeInsets(top: HorizontalBuffer, left: 0, bottom: HorizontalBuffer, right: HorizontalBuffer) + static let InsetForHeader = UIEdgeInsets(top: 0, left: HorizontalBuffer, bottom: 0, right: HorizontalBuffer) + static let InsetForFooter = UIEdgeInsets(top: VerticalBuffer, left: HorizontalBuffer, bottom: VerticalBuffer, right: HorizontalBuffer) + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Date.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Date.swift new file mode 100644 index 0000000000..0d7cde2d3d --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Date.swift @@ -0,0 +1,32 @@ +// +// Date.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 16/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +extension Date { + + static let iso8601Formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.calendar = Calendar(identifier: .iso8601) + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + return formatter + }() +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Info.plist b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Info.plist new file mode 100644 index 0000000000..c12df3b8a9 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/NetworkImageView.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/NetworkImageView.swift new file mode 100644 index 0000000000..aeb860d8aa --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/NetworkImageView.swift @@ -0,0 +1,57 @@ +// +// NetworkImageView.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 09/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +let imageCache = NSCache() + +class NetworkImageView: UIImageView { + + var imageUrlString: String? + + func loadImageUsingUrlString(urlString: String) { + + imageUrlString = urlString + + let url = URL(string: urlString) + + image = nil + + if let imageFromCache = imageCache.object(forKey: urlString as NSString) { + self.image = imageFromCache + return + } + + URLSession.shared.dataTask(with: url!, completionHandler: { (data, respones, error) in + + if error != nil { + print(error!) + return + } + + DispatchQueue.main.async { + let imageToCache = UIImage(data: data!) + if self.imageUrlString == urlString { + self.image = imageToCache + } + imageCache.setObject(imageToCache!, forKey: urlString as NSString) + } + }).resume() + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PX500Convenience.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PX500Convenience.swift new file mode 100644 index 0000000000..808a553d14 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PX500Convenience.swift @@ -0,0 +1,34 @@ +// +// PX500Convenience.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 08/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +func parsePopularPage(withURL: URL) -> Resource { + + let parse = Resource(url: withURL, parseJSON: { jsonData in + + guard let json = jsonData as? JSONDictionary, let photos = json["photos"] as? [JSONDictionary] else { return .failure(.errorParsingJSON) } + + guard let model = PopularPageModel(dictionary: json, photosArray: photos.flatMap(PhotoModel.init)) else { return .failure(.errorParsingJSON) } + + return .success(model) + }) + + return parse +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift new file mode 100644 index 0000000000..9a7cd52020 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift @@ -0,0 +1,124 @@ +// +// PhotoFeedModel.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 07/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +final class PhotoFeedModel { + + public private(set) var photoFeedModelType: PhotoFeedModelType + public private(set) var photos: [PhotoModel] = [] + public private(set) var imageSize: CGSize + private var url: URL + private var ids: [Int] = [] + private var currentPage: Int = 0 + private var totalPages: Int = 0 + public private(set) var totalItems: Int = 0 + private var fetchPageInProgress: Bool = false + private var refreshFeedInProgress: Bool = false + + init(initWithPhotoFeedModelType: PhotoFeedModelType, requiredImageSize: CGSize) { + self.photoFeedModelType = initWithPhotoFeedModelType + self.imageSize = requiredImageSize + self.url = URL.URLForFeedModelType(feedModelType: initWithPhotoFeedModelType) + } + + var numberOfItemsInFeed: Int { + return photos.count + } + + // return in completion handler the number of additions and the status of internet connection + + func updateNewBatchOfPopularPhotos(additionsAndConnectionStatusCompletion: @escaping (Int, InternetStatus) -> ()) { + + guard !fetchPageInProgress else { return } + + fetchPageInProgress = true + fetchNextPageOfPopularPhotos(replaceData: false) { [unowned self] additions, errors in + self.fetchPageInProgress = false + + if let error = errors { + switch error { + case .noInternetConnection: + additionsAndConnectionStatusCompletion(0, .noConnection) + default: additionsAndConnectionStatusCompletion(0, .connected) + } + } else { + additionsAndConnectionStatusCompletion(additions, .connected) + } + } + } + + private func fetchNextPageOfPopularPhotos(replaceData: Bool, numberOfAdditionsCompletion: @escaping (Int, NetworkingErrors?) -> ()) { + + if currentPage == totalPages, currentPage != 0 { + return numberOfAdditionsCompletion(0, .customError("No pages left to parse")) + } + + var newPhotos: [PhotoModel] = [] + var newIDs: [Int] = [] + + let pageToFetch = currentPage + 1 + + let url = self.url.addImageParameterForClosestImageSizeAndpage(size: imageSize, page: pageToFetch) + + WebService().load(resource: parsePopularPage(withURL: url)) { [unowned self] result in + + switch result { + case .success(let popularPage): + self.totalItems = popularPage.totalNumberOfItems + self.totalPages = popularPage.totalPages + self.currentPage = popularPage.page + + for photo in popularPage.photos { + if !replaceData || !self.ids.contains(photo.photoID) { + newPhotos.append(photo) + newIDs.append(photo.photoID) + } + } + + DispatchQueue.main.async { + if replaceData { + self.photos = newPhotos + self.ids = newIDs + } else { + self.photos += newPhotos + self.ids += newIDs + } + + numberOfAdditionsCompletion(newPhotos.count, nil) + } + + case .failure(let fail): + print(fail) + numberOfAdditionsCompletion(0, fail) + } + } + } +} + +enum PhotoFeedModelType { + case photoFeedModelTypePopular + case photoFeedModelTypeLocation + case photoFeedModelTypeUserPhotos +} + +enum InternetStatus { + case connected + case noConnection +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableNodeController.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableNodeController.swift new file mode 100644 index 0000000000..e8a0dce3ce --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableNodeController.swift @@ -0,0 +1,112 @@ +// +// PhotoFeedTableNodeController.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 06/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import AsyncDisplayKit + +class PhotoFeedTableNodeController: ASViewController { + + var activityIndicator: UIActivityIndicatorView! + var photoFeed: PhotoFeedModel + + init() { + photoFeed = PhotoFeedModel(initWithPhotoFeedModelType: .photoFeedModelTypePopular, requiredImageSize: screenSizeForWidth) + super.init(node: ASTableNode()) + self.navigationItem.title = "ASDK" + + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupActivityIndicator() + node.allowsSelection = false + node.view.separatorStyle = .none + node.dataSource = self + node.delegate = self + node.view.leadingScreensForBatching = 2.5 + navigationController?.hidesBarsOnSwipe = true + } + + // helper functions + func setupActivityIndicator() { + let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray) + self.activityIndicator = activityIndicator + let bounds = self.node.frame + var refreshRect = activityIndicator.frame + refreshRect.origin = CGPoint(x: (bounds.size.width - activityIndicator.frame.size.width) / 2.0, y: (bounds.size.height - activityIndicator.frame.size.height) / 2.0) + activityIndicator.frame = refreshRect + self.node.view.addSubview(activityIndicator) + } + + var screenSizeForWidth: CGSize = { + let screenRect = UIScreen.main.bounds + let screenScale = UIScreen.main.scale + return CGSize(width: screenRect.size.width * screenScale, height: screenRect.size.width * screenScale) + }() + + func fetchNewBatchWithContext(_ context: ASBatchContext?) { + activityIndicator.startAnimating() + photoFeed.updateNewBatchOfPopularPhotos() { additions, connectionStatus in + switch connectionStatus { + case .connected: + self.activityIndicator.stopAnimating() + self.addRowsIntoTableNode(newPhotoCount: additions) + context?.completeBatchFetching(true) + case .noConnection: + self.activityIndicator.stopAnimating() + if context != nil { + context!.completeBatchFetching(true) + } + break + } + } + } + + func addRowsIntoTableNode(newPhotoCount newPhotos: Int) { + let indexRange = (photoFeed.photos.count - newPhotos.. Int { + return photoFeed.numberOfItemsInFeed + } + + func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { + let photo = photoFeed.photos[indexPath.row] + let nodeBlock: ASCellNodeBlock = { _ in + return PhotoTableNodeCell(photoModel: photo) + } + return nodeBlock + } + + func shouldBatchFetchForCollectionNode(collectionNode: ASCollectionNode) -> Bool { + return true + } + + func tableNode(_ tableNode: ASTableNode, willBeginBatchFetchWith context: ASBatchContext) { + fetchNewBatchWithContext(context) + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableViewController.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableViewController.swift new file mode 100644 index 0000000000..0e7cbef975 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableViewController.swift @@ -0,0 +1,121 @@ +// +// PhotoFeedTableViewController.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 06/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class PhotoFeedTableViewController: UITableViewController { + + var activityIndicator: UIActivityIndicatorView! + var photoFeed: PhotoFeedModel + + init() { + photoFeed = PhotoFeedModel(initWithPhotoFeedModelType: .photoFeedModelTypePopular, requiredImageSize: screenSizeForWidth) + super.init(nibName: nil, bundle: nil) + self.navigationItem.title = "UIKit" + + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupActivityIndicator() + configureTableView() + fetchNewBatch() + navigationController?.hidesBarsOnSwipe = true + } + + func fetchNewBatch() { + activityIndicator.startAnimating() + photoFeed.updateNewBatchOfPopularPhotos() { additions, connectionStatus in + switch connectionStatus { + case .connected: + self.activityIndicator.stopAnimating() + self.addRowsIntoTableView(newPhotoCount: additions) + case .noConnection: + self.activityIndicator.stopAnimating() + break + } + } + } + + var screenSizeForWidth: CGSize = { + let screenRect = UIScreen.main.bounds + let screenScale = UIScreen.main.scale + return CGSize(width: screenRect.size.width * screenScale, height: screenRect.size.width * screenScale) + }() + + // helper functions + func setupActivityIndicator() { + let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray) + self.activityIndicator = activityIndicator + self.tableView.addSubview(activityIndicator) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + activityIndicator.centerXAnchor.constraint(equalTo: self.tableView.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: self.tableView.centerYAnchor) + ]) + } + + func configureTableView() { + tableView.register(PhotoTableViewCell.self, forCellReuseIdentifier: "photoCell") + tableView.allowsSelection = false + tableView.rowHeight = UITableViewAutomaticDimension + tableView.separatorStyle = .none + } +} + +extension PhotoFeedTableViewController { + + func addRowsIntoTableView(newPhotoCount newPhotos: Int) { + + let indexRange = (photoFeed.photos.count - newPhotos.. Int { + return photoFeed.photos.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "photoCell", for: indexPath) as? PhotoTableViewCell else { fatalError("Wrong cell type") } + cell.photoModel = photoFeed.photos[indexPath.row] + return cell + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return PhotoTableViewCell.height(for: photoFeed.photos[indexPath.row], withWidth: self.view.frame.size.width) + } + + override func scrollViewDidScroll(_ scrollView: UIScrollView) { + + let currentOffSetY = scrollView.contentOffset.y + let contentHeight = scrollView.contentSize.height + let screenHeight = UIScreen.main.bounds.size.height + let screenfullsBeforeBottom = (contentHeight - currentOffSetY) / screenHeight + if screenfullsBeforeBottom < 2.5 { + self.fetchNewBatch() + } + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoModel.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoModel.swift new file mode 100644 index 0000000000..e4070bb257 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoModel.swift @@ -0,0 +1,116 @@ +// +// PhotoModel.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 07/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +typealias JSONDictionary = [String : Any] + +struct PhotoModel { + + let url: String + let photoID: Int + let dateString: String + let descriptionText: String + let likesCount: Int + let ownerUserName: String + let ownerPicURL: String + + init?(dictionary: JSONDictionary) { + + guard let url = dictionary["image_url"] as? String, let date = dictionary["created_at"] as? String, let photoID = dictionary["id"] as? Int, let descriptionText = dictionary["name"] as? String, let likesCount = dictionary["positive_votes_count"] as? Int else { print("error parsing JSON within PhotoModel Init"); return nil } + + guard let user = dictionary["user"] as? JSONDictionary, let username = user["username"] as? String, let ownerPicURL = user["userpic_url"] as? String else { print("error parsing JSON within PhotoModel Init"); return nil } + + self.url = url + self.photoID = photoID + self.descriptionText = descriptionText + self.likesCount = likesCount + self.dateString = date + self.ownerUserName = username + self.ownerPicURL = ownerPicURL + } +} + +extension PhotoModel { + + // MARK: - Attributed Strings + + func attrStringForUserName(withSize size: CGFloat) -> NSAttributedString { + let attr = [ + NSForegroundColorAttributeName : UIColor.darkGray, + NSFontAttributeName: UIFont.boldSystemFont(ofSize: size) + ] + return NSAttributedString(string: self.ownerUserName, attributes: attr) + } + + func attrStringForDescription(withSize size: CGFloat) -> NSAttributedString { + let attr = [ + NSForegroundColorAttributeName : UIColor.darkGray, + NSFontAttributeName: UIFont.systemFont(ofSize: size) + ] + return NSAttributedString(string: self.descriptionText, attributes: attr) + } + + func attrStringLikes(withSize size: CGFloat) -> NSAttributedString { + + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + let formattedLikesNumber: String? = formatter.string(from: NSNumber(value: self.likesCount)) + let likesString: String = "\(formattedLikesNumber!) Likes" + let textAttr = [NSForegroundColorAttributeName : UIColor.mainBarTintColor(), NSFontAttributeName: UIFont.systemFont(ofSize: size)] + let likesAttrString = NSAttributedString(string: likesString, attributes: textAttr) + + let heartAttr = [NSForegroundColorAttributeName : UIColor.red, NSFontAttributeName: UIFont.systemFont(ofSize: size)] + let heartAttrString = NSAttributedString(string: "♥︎ ", attributes: heartAttr) + + let combine = NSMutableAttributedString() + combine.append(heartAttrString) + combine.append(likesAttrString) + return combine + } + + func attrStringForTimeSinceString(withSize size: CGFloat) -> NSAttributedString { + + let attr = [ + NSForegroundColorAttributeName : UIColor.mainBarTintColor(), + NSFontAttributeName: UIFont.systemFont(ofSize: size) + ] + + let date = Date.iso8601Formatter.date(from: self.dateString)! + return NSAttributedString(string: timeStringSince(fromConverted: date), attributes: attr) + } + + private func timeStringSince(fromConverted date: Date) -> String { + let diffDates = NSCalendar.current.dateComponents([.day, .hour, .second], from: date, to: Date()) + + if let week = diffDates.day, week > 7 { + return "\(week / 7)w" + } else if let day = diffDates.day, day > 0 { + return "\(day)d" + } else if let hour = diffDates.hour, hour > 0 { + return "\(hour)h" + } else if let second = diffDates.second, second > 0 { + return "\(second)s" + } else if let zero = diffDates.second, zero == 0 { + return "1s" + } else { + return "ERROR" + } + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableNodeCell.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableNodeCell.swift new file mode 100644 index 0000000000..56e02588b5 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableNodeCell.swift @@ -0,0 +1,84 @@ +// +// PhotoTableNodeCell.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 09/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.// + +import Foundation +import AsyncDisplayKit + +class PhotoTableNodeCell: ASCellNode { + + let usernameLabel = ASTextNode() + let timeIntervalLabel = ASTextNode() + let photoLikesLabel = ASTextNode() + let photoDescriptionLabel = ASTextNode() + + let avatarImageNode: ASNetworkImageNode = { + let imageNode = ASNetworkImageNode() + imageNode.contentMode = .scaleAspectFill + imageNode.imageModificationBlock = ASImageNodeRoundBorderModificationBlock(0, nil) + return imageNode + }() + + let photoImageNode: ASNetworkImageNode = { + let imageNode = ASNetworkImageNode() + imageNode.contentMode = .scaleAspectFill + return imageNode + }() + + init(photoModel: PhotoModel) { + super.init() + self.photoImageNode.url = URL(string: photoModel.url) + self.avatarImageNode.url = URL(string: photoModel.ownerPicURL) + self.usernameLabel.attributedText = photoModel.attrStringForUserName(withSize: Constants.CellLayout.FontSize) + self.timeIntervalLabel.attributedText = photoModel.attrStringForTimeSinceString(withSize: Constants.CellLayout.FontSize) + self.photoLikesLabel.attributedText = photoModel.attrStringLikes(withSize: Constants.CellLayout.FontSize) + self.photoDescriptionLabel.attributedText = photoModel.attrStringForDescription(withSize: Constants.CellLayout.FontSize) + self.automaticallyManagesSubnodes = true + } + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + + // Header Stack + + var headerChildren: [ASLayoutElement] = [] + + let headerStack = ASStackLayoutSpec.horizontal() + headerStack.alignItems = .center + avatarImageNode.style.preferredSize = CGSize(width: Constants.CellLayout.UserImageHeight, height: Constants.CellLayout.UserImageHeight) + headerChildren.append(ASInsetLayoutSpec(insets: Constants.CellLayout.InsetForAvatar, child: avatarImageNode)) + usernameLabel.style.flexShrink = 1.0 + headerChildren.append(usernameLabel) + + let spacer = ASLayoutSpec() + spacer.style.flexGrow = 1.0 + headerChildren.append(spacer) + + timeIntervalLabel.style.spacingBefore = Constants.CellLayout.HorizontalBuffer + headerChildren.append(timeIntervalLabel) + + let footerStack = ASStackLayoutSpec.vertical() + footerStack.spacing = Constants.CellLayout.VerticalBuffer + footerStack.children = [photoLikesLabel, photoDescriptionLabel] + headerStack.children = headerChildren + + let verticalStack = ASStackLayoutSpec.vertical() + + verticalStack.children = [ASInsetLayoutSpec(insets: Constants.CellLayout.InsetForHeader, child: headerStack), ASRatioLayoutSpec(ratio: 1.0, child: photoImageNode), ASInsetLayoutSpec(insets: Constants.CellLayout.InsetForFooter, child: footerStack)] + + return verticalStack + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableViewCell.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableViewCell.swift new file mode 100644 index 0000000000..b57beab9a0 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableViewCell.swift @@ -0,0 +1,142 @@ +// +// PhotoTableViewCell.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 08/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class PhotoTableViewCell: UITableViewCell { + + var photoModel: PhotoModel? { + didSet { + if let model = photoModel { + photoImageView.loadImageUsingUrlString(urlString: model.url) + avatarImageView.loadImageUsingUrlString(urlString: model.ownerPicURL) + photoLikesLabel.attributedText = model.attrStringLikes(withSize: Constants.CellLayout.FontSize) + usernameLabel.attributedText = model.attrStringForUserName(withSize: Constants.CellLayout.FontSize) + timeIntervalLabel.attributedText = model.attrStringForTimeSinceString(withSize: Constants.CellLayout.FontSize) + photoDescriptionLabel.attributedText = model.attrStringForDescription(withSize: Constants.CellLayout.FontSize) + photoDescriptionLabel.sizeToFit() + var rect = photoDescriptionLabel.frame + let availableWidth = self.bounds.size.width - Constants.CellLayout.HorizontalBuffer * 2 + rect.size = model.attrStringForDescription(withSize: Constants.CellLayout.FontSize).boundingRect(with: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil).size + photoDescriptionLabel.frame = rect + } + } + } + + let photoImageView: NetworkImageView = { + let imageView = NetworkImageView() + imageView.contentMode = .scaleAspectFill + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + let avatarImageView: NetworkImageView = { + let imageView = NetworkImageView() + imageView.contentMode = .scaleAspectFill + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.layer.cornerRadius = Constants.CellLayout.UserImageHeight / 2 + imageView.clipsToBounds = true + return imageView + }() + + let usernameLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + let timeIntervalLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + let photoLikesLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + let photoDescriptionLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 3 + return label + }() + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupViews() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupViews() { + addSubview(photoImageView) + addSubview(avatarImageView) + addSubview(usernameLabel) + addSubview(timeIntervalLabel) + addSubview(photoLikesLabel) + addSubview(photoDescriptionLabel) + setupConstraints() + } + + func setupConstraints() { + + NSLayoutConstraint.activate ([ + //photoImageView + photoImageView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.CellLayout.HeaderHeight), + photoImageView.widthAnchor.constraint(equalTo: widthAnchor), + photoImageView.heightAnchor.constraint(equalTo: photoImageView.widthAnchor), + // avatarImageView + avatarImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: Constants.CellLayout.HorizontalBuffer), + avatarImageView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.CellLayout.HorizontalBuffer), + avatarImageView.heightAnchor.constraint(equalToConstant: Constants.CellLayout.UserImageHeight), + avatarImageView.widthAnchor.constraint(equalTo: avatarImageView.heightAnchor), + // usernameLabel + usernameLabel.leftAnchor.constraint(equalTo: avatarImageView.rightAnchor, constant: Constants.CellLayout.HorizontalBuffer), + usernameLabel.rightAnchor.constraint(equalTo: timeIntervalLabel.leftAnchor, constant: -Constants.CellLayout.HorizontalBuffer), + usernameLabel.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor), + // timeIntervalLabel + timeIntervalLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -Constants.CellLayout.HorizontalBuffer), + timeIntervalLabel.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor), + // photoLikesLabel + photoLikesLabel.topAnchor.constraint(equalTo: photoImageView.bottomAnchor, constant: Constants.CellLayout.VerticalBuffer), + photoLikesLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: Constants.CellLayout.HorizontalBuffer), + // photoDescriptionLabel + photoDescriptionLabel.topAnchor.constraint(equalTo: photoLikesLabel.bottomAnchor, constant: Constants.CellLayout.VerticalBuffer), + photoDescriptionLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: Constants.CellLayout.HorizontalBuffer), + photoDescriptionLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -Constants.CellLayout.HorizontalBuffer), + photoDescriptionLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.CellLayout.VerticalBuffer) + ]) + } + + class func height(for photo: PhotoModel, withWidth width: CGFloat) -> CGFloat { + let photoHeight = width + let font = UIFont.systemFont(ofSize: Constants.CellLayout.FontSize) + let likesHeight = round(font.lineHeight) + let descriptionAttrString = photo.attrStringForDescription(withSize: Constants.CellLayout.FontSize) + let availableWidth = width - Constants.CellLayout.HorizontalBuffer * 2 + let descriptionHeight = descriptionAttrString.boundingRect(with: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil).size.height + + return likesHeight + descriptionHeight + photoHeight + Constants.CellLayout.HeaderHeight + Constants.CellLayout.VerticalBuffer * 3 + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PopularPageModel.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PopularPageModel.swift new file mode 100644 index 0000000000..8aca2445f1 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PopularPageModel.swift @@ -0,0 +1,37 @@ +// +// PopularPageModel.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 08/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +class PopularPageModel: NSObject { + + let page: Int + let totalPages: Int + let totalNumberOfItems: Int + let photos: [PhotoModel] + + init?(dictionary: JSONDictionary, photosArray: [PhotoModel]) { + guard let page = dictionary["current_page"] as? Int, let totalPages = dictionary["total_pages"] as? Int, let totalItems = dictionary["total_items"] as? Int else { print("error parsing JSON within PhotoModel Init"); return nil } + + self.page = page + self.totalPages = totalPages + self.totalNumberOfItems = totalItems + self.photos = photosArray + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIColor.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIColor.swift new file mode 100644 index 0000000000..5ab9d7e010 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIColor.swift @@ -0,0 +1,26 @@ +// +// UIColor.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 06/01/2017. +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +extension UIColor { + + class func mainBarTintColor() -> UIColor { + return UIColor(red: 69/255, green: 142/255, blue: 255/255, alpha: 1) + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIImage.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIImage.swift new file mode 100644 index 0000000000..4eb233f37b --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIImage.swift @@ -0,0 +1,60 @@ +// +// UIImage.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 18/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +// This extension was copied directly from LayoutSpecExamples-Swift. It is an example of how to create Precomoposed Alpha Corners. I have used the helper ASImageNodeRoundBorderModificationBlock:boarderWidth:boarderColor function in practice which does the same. + +extension UIImage { + + func makeCircularImage(size: CGSize, borderWidth width: CGFloat) -> UIImage { + // make a CGRect with the image's size + let circleRect = CGRect(origin: .zero, size: size) + + // begin the image context since we're not in a drawRect: + UIGraphicsBeginImageContextWithOptions(circleRect.size, false, 0) + + // create a UIBezierPath circle + let circle = UIBezierPath(roundedRect: circleRect, cornerRadius: circleRect.size.width * 0.5) + + // clip to the circle + circle.addClip() + + UIColor.white.set() + circle.fill() + + // draw the image in the circleRect *AFTER* the context is clipped + self.draw(in: circleRect) + + // create a border (for white background pictures) + if width > 0 { + circle.lineWidth = width + UIColor.white.set() + circle.stroke() + } + + // get an image from the image context + let roundedImage = UIGraphicsGetImageFromCurrentImageContext() + + // end the image context since we're not in a drawRect: + UIGraphicsEndImageContext() + + return roundedImage ?? self + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/URL.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/URL.swift new file mode 100644 index 0000000000..5960498155 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/URL.swift @@ -0,0 +1,67 @@ +// +// URL.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 07/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +extension URL { + + static func URLForFeedModelType(feedModelType: PhotoFeedModelType) -> URL { + switch feedModelType { + case .photoFeedModelTypePopular: + return URL(string: assemble500PXURLString(endpoint: Constants.PX500.URLS.PopularEndpoint))! + + case .photoFeedModelTypeLocation: + return URL(string: assemble500PXURLString(endpoint: Constants.PX500.URLS.SearchEndpoint))! + + case .photoFeedModelTypeUserPhotos: + return URL(string: assemble500PXURLString(endpoint: Constants.PX500.URLS.UserEndpoint))! + } + } + + private static func assemble500PXURLString(endpoint: String) -> String { + return Constants.PX500.URLS.Host + endpoint + Constants.PX500.URLS.ConsumerKey + } + + mutating func addImageParameterForClosestImageSizeAndpage(size: CGSize, page: Int) -> URL { + + let imageParameterID: Int + + if size.height <= 70 { + imageParameterID = 1 + } else if size.height <= 100 { + imageParameterID = 100 + } else if size.height <= 140 { + imageParameterID = 2 + } else if size.height <= 200 { + imageParameterID = 200 + } else if size.height <= 280 { + imageParameterID = 3 + } else if size.height <= 400 { + imageParameterID = 400 + } else { + imageParameterID = 600 + } + + var urlString = self.absoluteString + urlString.append("&image_size=\(imageParameterID)&page=\(page)") + + return URL(string: urlString)! + } + +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Webservice.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Webservice.swift new file mode 100644 index 0000000000..e2196208d2 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Webservice.swift @@ -0,0 +1,93 @@ +// +// Webservice.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 06/01/2017. +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// swiftlint:disable force_cast + +import UIKit + +final class WebService { + func load(resource: Resource, completion: @escaping (Result) -> ()) { + URLSession.shared.dataTask(with: resource.url) { data, response, error in + // Check for errors in responses. + let result = self.checkForNetworkErrors(data, response, error) + + switch result { + case .success(let data): + completion(resource.parse(data)) + case .failure(let error): + completion(.failure(error)) + } + }.resume() + } +} + +extension WebService { + + fileprivate func checkForNetworkErrors(_ data: Data?, _ response: URLResponse?, _ error: Error?) -> Result { + // Check for errors in responses. + guard error == nil else { + if (error as! NSError).domain == NSURLErrorDomain && ((error as! NSError).code == NSURLErrorNotConnectedToInternet || (error as! NSError).code == NSURLErrorTimedOut) { + return .failure(.noInternetConnection) + } else { + return .failure(.returnedError(error!)) + } + } + + guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else { + return .failure((.invalidStatusCode("Request returned status code other than 2xx \(response)"))) + } + + guard let data = data else { return .failure(.dataReturnedNil) } + + return .success(data) + } +} + +struct Resource { + let url: URL + let parse: (Data) -> Result +} + +extension Resource { + + init(url: URL, parseJSON: @escaping (Any) -> Result) { + self.url = url + self.parse = { data in + do { + let jsonData = try JSONSerialization.jsonObject(with: data, options: []) + return parseJSON(jsonData) + } catch { + fatalError("Error parsing data") + } + } + } +} + +enum Result { + case success(T) + case failure(NetworkingErrors) +} + +enum NetworkingErrors: Error { + case errorParsingJSON + case noInternetConnection + case dataReturnedNil + case returnedError(Error) + case invalidStatusCode(String) + case customError(String) +} diff --git a/examples_extra/ASDKgram-Swift/Podfile b/examples_extra/ASDKgram-Swift/Podfile new file mode 100644 index 0000000000..9e67970879 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/Podfile @@ -0,0 +1,8 @@ + +target 'ASDKgram-Swift' do + + use_frameworks! + + pod 'AsyncDisplayKit', '>= 2.0' + +end diff --git a/examples_extra/ASLayoutSpecPlayground-Swift/Podfile b/examples_extra/ASLayoutSpecPlayground-Swift/Podfile index dcbb49798f..44f51a2a4c 100644 --- a/examples_extra/ASLayoutSpecPlayground-Swift/Podfile +++ b/examples_extra/ASLayoutSpecPlayground-Swift/Podfile @@ -1,7 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.1' +platform :ios, '8.0' use_frameworks! target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end - diff --git a/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..919434a625 --- /dev/null +++ b/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples_extra/ASTableViewStressTest/Podfile b/examples_extra/ASTableViewStressTest/Podfile index 919de4b311..defaf55058 100644 --- a/examples_extra/ASTableViewStressTest/Podfile +++ b/examples_extra/ASTableViewStressTest/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples_extra/ASTraitCollection/Podfile b/examples_extra/ASTraitCollection/Podfile index 919de4b311..defaf55058 100644 --- a/examples_extra/ASTraitCollection/Podfile +++ b/examples_extra/ASTraitCollection/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj b/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj index f9e679bb36..20186b37ad 100644 --- a/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj @@ -339,7 +339,6 @@ baseConfigurationReference = 056298286C03B7760575CC56 /* Pods-Sample.debug.xcconfig */; buildSettings = { INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -351,7 +350,6 @@ baseConfigurationReference = A7F0013FBBCBEA0C9FB68986 /* Pods-Sample.release.xcconfig */; buildSettings = { INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/examples_extra/ASTraitCollection/Sample/OverrideViewController.m b/examples_extra/ASTraitCollection/Sample/OverrideViewController.m index 7ca5f877f4..008497f9d8 100644 --- a/examples_extra/ASTraitCollection/Sample/OverrideViewController.m +++ b/examples_extra/ASTraitCollection/Sample/OverrideViewController.m @@ -39,7 +39,7 @@ - (instancetype)init [self addSubnode:_textNode]; _buttonNode = [[ASButtonNode alloc] init]; - [_buttonNode setAttributedTitle:[[NSAttributedString alloc] initWithString:@"Close"] forState:ASControlStateNormal]; + [_buttonNode setAttributedTitle:[[NSAttributedString alloc] initWithString:@"Close"] forState:UIControlStateNormal]; [self addSubnode:_buttonNode]; self.backgroundColor = [UIColor lightGrayColor]; diff --git a/examples_extra/BackgroundPropertySetting/Podfile b/examples_extra/BackgroundPropertySetting/Podfile index e077db444a..44f51a2a4c 100644 --- a/examples_extra/BackgroundPropertySetting/Podfile +++ b/examples_extra/BackgroundPropertySetting/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' use_frameworks! target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples_extra/CollectionViewWithViewControllerCells/Podfile b/examples_extra/CollectionViewWithViewControllerCells/Podfile index 919de4b311..defaf55058 100644 --- a/examples_extra/CollectionViewWithViewControllerCells/Podfile +++ b/examples_extra/CollectionViewWithViewControllerCells/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj b/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj index c6fe60b900..0c20d49148 100644 --- a/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj @@ -1,826 +1,379 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 25A1FA831C02F7AC00193875 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - MosaicCollectionViewLayout.h - sourceTree - <group> - - 25A1FA841C02F7AC00193875 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - MosaicCollectionViewLayout.m - sourceTree - <group> - - 25A1FA851C02F7AC00193875 - - fileRef - 25A1FA841C02F7AC00193875 - isa - PBXBuildFile - - 4E5B5F4E697ED7E9DB2D6324 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - 637D7C9FD46862FB6060DE4D - - fileRef - D9DB64B734017B22EADB64FD - isa - PBXBuildFile - - 90A2B9C5397C46134C8A793B - - children - - F594729764C63FA050734ED5 - D61D292E4C992F2B47A062A3 - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 9B92C87F1BC17D3000EE46B2 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - SupplementaryNode.h - sourceTree - <group> - - 9B92C8801BC17D3000EE46B2 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - SupplementaryNode.m - sourceTree - <group> - - 9B92C8811BC17D3000EE46B2 - - fileRef - 9B92C8801BC17D3000EE46B2 - isa - PBXBuildFile - - 9BA2CEA01BB2579C00D18414 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - file.storyboard - path - Launchboard.storyboard - sourceTree - <group> - - 9BA2CEA11BB2579C00D18414 - - fileRef - 9BA2CEA01BB2579C00D18414 - isa - PBXBuildFile - - A6902C454C7661D0D277AC62 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - AC3C4A551A11F47200143C57 - - children - - AC3C4A601A11F47200143C57 - AC3C4A5F1A11F47200143C57 - 90A2B9C5397C46134C8A793B - D6E38FF0CB18E3F55CF06437 - - isa - PBXGroup - sourceTree - <group> - - AC3C4A561A11F47200143C57 - - attributes - - LastUpgradeCheck - 0610 - ORGANIZATIONNAME - Facebook - TargetAttributes - - AC3C4A5D1A11F47200143C57 - - CreatedOnToolsVersion - 6.1 - - - - buildConfigurationList - AC3C4A591A11F47200143C57 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - AC3C4A551A11F47200143C57 - productRefGroup - AC3C4A5F1A11F47200143C57 - projectDirPath - - projectReferences - - projectRoot - - targets - - AC3C4A5D1A11F47200143C57 - - - AC3C4A591A11F47200143C57 - - buildConfigurations - - AC3C4A7F1A11F47200143C57 - AC3C4A801A11F47200143C57 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - AC3C4A5A1A11F47200143C57 - - buildActionMask - 2147483647 - files - - 25A1FA851C02F7AC00193875 - AC3C4A6A1A11F47200143C57 - 9B92C8811BC17D3000EE46B2 - AC3C4A671A11F47200143C57 - AC3C4A641A11F47200143C57 - AEE6B3E51C16B65600238D20 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - AC3C4A5B1A11F47200143C57 - - buildActionMask - 2147483647 - files - - 637D7C9FD46862FB6060DE4D - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - AC3C4A5C1A11F47200143C57 - - buildActionMask - 2147483647 - files - - 9BA2CEA11BB2579C00D18414 - AC3C4A8E1A11F80C00143C57 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - AC3C4A5D1A11F47200143C57 - - buildConfigurationList - AC3C4A811A11F47200143C57 - buildPhases - - F868CFBB21824CC9521B6588 - AC3C4A5A1A11F47200143C57 - AC3C4A5B1A11F47200143C57 - AC3C4A5C1A11F47200143C57 - A6902C454C7661D0D277AC62 - 4E5B5F4E697ED7E9DB2D6324 - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - AC3C4A5E1A11F47200143C57 - productType - com.apple.product-type.application - - AC3C4A5E1A11F47200143C57 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - AC3C4A5F1A11F47200143C57 - - children - - AC3C4A5E1A11F47200143C57 - - isa - PBXGroup - name - Products - sourceTree - <group> - - AC3C4A601A11F47200143C57 - - children - - 25A1FA831C02F7AC00193875 - 25A1FA841C02F7AC00193875 - AC3C4A651A11F47200143C57 - AC3C4A661A11F47200143C57 - AC3C4A681A11F47200143C57 - AC3C4A691A11F47200143C57 - AC3C4A8D1A11F80C00143C57 - AC3C4A611A11F47200143C57 - 9B92C87F1BC17D3000EE46B2 - 9B92C8801BC17D3000EE46B2 - AEE6B3E31C16B65600238D20 - AEE6B3E41C16B65600238D20 - - indentWidth - 2 - isa - PBXGroup - path - Sample - sourceTree - <group> - tabWidth - 2 - usesTabs - 0 - - AC3C4A611A11F47200143C57 - - children - - AC3C4A621A11F47200143C57 - AC3C4A631A11F47200143C57 - 9BA2CEA01BB2579C00D18414 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - AC3C4A621A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - AC3C4A631A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - AC3C4A641A11F47200143C57 - - fileRef - AC3C4A631A11F47200143C57 - isa - PBXBuildFile - - AC3C4A651A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - AC3C4A661A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - AC3C4A671A11F47200143C57 - - fileRef - AC3C4A661A11F47200143C57 - isa - PBXBuildFile - - AC3C4A681A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - AC3C4A691A11F47200143C57 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - AC3C4A6A1A11F47200143C57 - - fileRef - AC3C4A691A11F47200143C57 - isa - PBXBuildFile - - AC3C4A7F1A11F47200143C57 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_SYMBOLS_PRIVATE_EXTERN - NO - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Debug - - AC3C4A801A11F47200143C57 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - YES - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - TARGETED_DEVICE_FAMILY - 1,2 - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - AC3C4A811A11F47200143C57 - - buildConfigurations - - AC3C4A821A11F47200143C57 - AC3C4A831A11F47200143C57 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - AC3C4A821A11F47200143C57 - - baseConfigurationReference - F594729764C63FA050734ED5 - buildSettings - - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME - LaunchImage - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1 - - isa - XCBuildConfiguration - name - Debug - - AC3C4A831A11F47200143C57 - - baseConfigurationReference - D61D292E4C992F2B47A062A3 - buildSettings - - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME - LaunchImage - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 8.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1 - - isa - XCBuildConfiguration - name - Release - - AC3C4A8D1A11F80C00143C57 - - isa - PBXFileReference - lastKnownFileType - folder.assetcatalog - path - Images.xcassets - sourceTree - <group> - - AC3C4A8E1A11F80C00143C57 - - fileRef - AC3C4A8D1A11F80C00143C57 - isa - PBXBuildFile - - AEE6B3E31C16B65600238D20 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ImageViewController.h - sourceTree - <group> - - AEE6B3E41C16B65600238D20 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ImageViewController.m - sourceTree - <group> - - AEE6B3E51C16B65600238D20 - - fileRef - AEE6B3E41C16B65600238D20 - isa - PBXBuildFile - - D61D292E4C992F2B47A062A3 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - D6E38FF0CB18E3F55CF06437 - - children - - D9DB64B734017B22EADB64FD - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - D9DB64B734017B22EADB64FD - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - F594729764C63FA050734ED5 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - F868CFBB21824CC9521B6588 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - - rootObject - AC3C4A561A11F47200143C57 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 25A1FA851C02F7AC00193875 /* MosaicCollectionViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */; }; + 637D7C9FD46862FB6060DE4D /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D9DB64B734017B22EADB64FD /* libPods-Sample.a */; }; + 9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */; }; + 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; }; + AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; }; + AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; }; + AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; }; + AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; }; + AEE6B3E51C16B65600238D20 /* ImageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AEE6B3E41C16B65600238D20 /* ImageViewController.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 25A1FA831C02F7AC00193875 /* MosaicCollectionViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MosaicCollectionViewLayout.h; sourceTree = ""; }; + 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MosaicCollectionViewLayout.m; sourceTree = ""; }; + 9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SupplementaryNode.h; sourceTree = ""; }; + 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SupplementaryNode.m; sourceTree = ""; }; + 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Launchboard.storyboard; sourceTree = ""; }; + AC3C4A5E1A11F47200143C57 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + AC3C4A621A11F47200143C57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AC3C4A631A11F47200143C57 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + AC3C4A651A11F47200143C57 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + AC3C4A661A11F47200143C57 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + AC3C4A681A11F47200143C57 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + AC3C4A691A11F47200143C57 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + AEE6B3E31C16B65600238D20 /* ImageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageViewController.h; sourceTree = ""; }; + AEE6B3E41C16B65600238D20 /* ImageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageViewController.m; sourceTree = ""; }; + D61D292E4C992F2B47A062A3 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + D9DB64B734017B22EADB64FD /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + F594729764C63FA050734ED5 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + AC3C4A5B1A11F47200143C57 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 637D7C9FD46862FB6060DE4D /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 90A2B9C5397C46134C8A793B /* Pods */ = { + isa = PBXGroup; + children = ( + F594729764C63FA050734ED5 /* Pods-Sample.debug.xcconfig */, + D61D292E4C992F2B47A062A3 /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + AC3C4A551A11F47200143C57 = { + isa = PBXGroup; + children = ( + AC3C4A601A11F47200143C57 /* Sample */, + AC3C4A5F1A11F47200143C57 /* Products */, + 90A2B9C5397C46134C8A793B /* Pods */, + D6E38FF0CB18E3F55CF06437 /* Frameworks */, + ); + sourceTree = ""; + }; + AC3C4A5F1A11F47200143C57 /* Products */ = { + isa = PBXGroup; + children = ( + AC3C4A5E1A11F47200143C57 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + AC3C4A601A11F47200143C57 /* Sample */ = { + isa = PBXGroup; + children = ( + 25A1FA831C02F7AC00193875 /* MosaicCollectionViewLayout.h */, + 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */, + AC3C4A651A11F47200143C57 /* AppDelegate.h */, + AC3C4A661A11F47200143C57 /* AppDelegate.m */, + AC3C4A681A11F47200143C57 /* ViewController.h */, + AC3C4A691A11F47200143C57 /* ViewController.m */, + AC3C4A8D1A11F80C00143C57 /* Images.xcassets */, + AC3C4A611A11F47200143C57 /* Supporting Files */, + 9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */, + 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */, + AEE6B3E31C16B65600238D20 /* ImageViewController.h */, + AEE6B3E41C16B65600238D20 /* ImageViewController.m */, + ); + indentWidth = 2; + path = Sample; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + AC3C4A611A11F47200143C57 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + AC3C4A621A11F47200143C57 /* Info.plist */, + AC3C4A631A11F47200143C57 /* main.m */, + 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D6E38FF0CB18E3F55CF06437 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D9DB64B734017B22EADB64FD /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + AC3C4A5D1A11F47200143C57 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */, + AC3C4A5A1A11F47200143C57 /* Sources */, + AC3C4A5B1A11F47200143C57 /* Frameworks */, + AC3C4A5C1A11F47200143C57 /* Resources */, + A6902C454C7661D0D277AC62 /* Copy Pods Resources */, + 4E5B5F4E697ED7E9DB2D6324 /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = AC3C4A5E1A11F47200143C57 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + AC3C4A561A11F47200143C57 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + AC3C4A5D1A11F47200143C57 = { + CreatedOnToolsVersion = 6.1; + }; + }; + }; + buildConfigurationList = AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = AC3C4A551A11F47200143C57; + productRefGroup = AC3C4A5F1A11F47200143C57 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + AC3C4A5D1A11F47200143C57 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + AC3C4A5C1A11F47200143C57 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */, + AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 4E5B5F4E697ED7E9DB2D6324 /* 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-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + A6902C454C7661D0D277AC62 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + AC3C4A5A1A11F47200143C57 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 25A1FA851C02F7AC00193875 /* MosaicCollectionViewLayout.m in Sources */, + AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */, + 9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */, + AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */, + AC3C4A641A11F47200143C57 /* main.m in Sources */, + AEE6B3E51C16B65600238D20 /* ImageViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + AC3C4A7F1A11F47200143C57 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AC3C4A801A11F47200143C57 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + AC3C4A821A11F47200143C57 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F594729764C63FA050734ED5 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + AC3C4A831A11F47200143C57 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D61D292E4C992F2B47A062A3 /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AC3C4A7F1A11F47200143C57 /* Debug */, + AC3C4A801A11F47200143C57 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AC3C4A821A11F47200143C57 /* Debug */, + AC3C4A831A11F47200143C57 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = AC3C4A561A11F47200143C57 /* Project object */; +} diff --git a/examples_extra/EditableText/Podfile b/examples_extra/EditableText/Podfile index 5c30ce798e..7a8d8c1a00 100644 --- a/examples_extra/EditableText/Podfile +++ b/examples_extra/EditableText/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples_extra/Multiplex/Podfile b/examples_extra/Multiplex/Podfile index 919de4b311..defaf55058 100644 --- a/examples_extra/Multiplex/Podfile +++ b/examples_extra/Multiplex/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples_extra/Multiplex/Sample/ScreenNode.m b/examples_extra/Multiplex/Sample/ScreenNode.m index 06ae9e7dcc..ab0f94f394 100644 --- a/examples_extra/Multiplex/Sample/ScreenNode.m +++ b/examples_extra/Multiplex/Sample/ScreenNode.m @@ -71,7 +71,7 @@ - (void)setText:(NSString *)text NSDictionary *attributes = @{NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:22.0f]}; NSAttributedString *string = [[NSAttributedString alloc] initWithString:text attributes:attributes]; - [_buttonNode setAttributedTitle:string forState:ASControlStateNormal]; + [_buttonNode setAttributedTitle:string forState:UIControlStateNormal]; [self setNeedsLayout]; } @@ -124,10 +124,10 @@ - (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode didFinishDownloadin #pragma mark - #pragma mark ASImageDownloaderProtocol. -- (id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgressBlock:(void (^)(CGFloat progress))downloadProgressBlock - completion:(void (^)(CGImageRef image, NSError *error))completion +- (nullable id)downloadImageWithURL:(NSURL *)URL + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgress:(nullable ASImageDownloaderProgress)downloadProgressBlock + completion:(ASImageDownloaderCompletion)completion { // if no callback queue is supplied, run on the main thread if (callbackQueue == nil) { @@ -146,7 +146,7 @@ - (id)downloadImageWithURL:(NSURL *)URL } if (completion) { - completion([[UIImage imageWithData:data] CGImage], connectionError); + completion([UIImage imageWithData:data], connectionError, nil); } }); }; diff --git a/examples_extra/Placeholders/Podfile b/examples_extra/Placeholders/Podfile index 919de4b311..defaf55058 100644 --- a/examples_extra/Placeholders/Podfile +++ b/examples_extra/Placeholders/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj b/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj index af976bea62..e360fd6cd7 100644 --- a/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj @@ -1,894 +1,389 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 0585427F19D4DBE100606EA6 - - isa - PBXFileReference - lastKnownFileType - image.png - name - Default-568h@2x.png - path - ../Default-568h@2x.png - sourceTree - <group> - - 0585428019D4DBE100606EA6 - - fileRef - 0585427F19D4DBE100606EA6 - isa - PBXBuildFile - - 05E2127819D4DB510098F589 - - children - - 05E2128319D4DB510098F589 - 05E2128219D4DB510098F589 - 1A943BF0259746F18D6E423F - 1AE410B73DA5C3BD087ACDD7 - - indentWidth - 2 - isa - PBXGroup - sourceTree - <group> - tabWidth - 2 - usesTabs - 0 - - 05E2127919D4DB510098F589 - - attributes - - LastUpgradeCheck - 0600 - ORGANIZATIONNAME - Facebook - TargetAttributes - - 05E2128019D4DB510098F589 - - CreatedOnToolsVersion - 6.0.1 - - - - buildConfigurationList - 05E2127C19D4DB510098F589 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - 05E2127819D4DB510098F589 - productRefGroup - 05E2128219D4DB510098F589 - projectDirPath - - projectReferences - - projectRoot - - targets - - 05E2128019D4DB510098F589 - - - 05E2127C19D4DB510098F589 - - buildConfigurations - - 05E212A219D4DB510098F589 - 05E212A319D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E2127D19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 29A7F3A51A3638DE00CF34F2 - 29E35E9B1A2F8DB0007B4B17 - 29E35E9E1A2F8DBC007B4B17 - 05E2128D19D4DB510098F589 - 05E2128A19D4DB510098F589 - 29E35EA31A2FD0E9007B4B17 - 05E2128719D4DB510098F589 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127E19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 37563A5CF6FA5205CCAB18B2 - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127F19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 29E35EA01A2F9650007B4B17 - 0585428019D4DBE100606EA6 - 6C2C82AC19EE274300767484 - 6C2C82AD19EE274300767484 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2128019D4DB510098F589 - - buildConfigurationList - 05E212A419D4DB510098F589 - buildPhases - - E080B80F89C34A25B3488E26 - 05E2127D19D4DB510098F589 - 05E2127E19D4DB510098F589 - 05E2127F19D4DB510098F589 - F012A6F39E0149F18F564F50 - 8DF84F9CA640D8BF98C61D50 - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - 05E2128119D4DB510098F589 - productType - com.apple.product-type.application - - 05E2128119D4DB510098F589 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - 05E2128219D4DB510098F589 - - children - - 05E2128119D4DB510098F589 - - isa - PBXGroup - name - Products - sourceTree - <group> - - 05E2128319D4DB510098F589 - - children - - 05E2128819D4DB510098F589 - 05E2128919D4DB510098F589 - 29E35E9F1A2F9650007B4B17 - 29E35EA11A2FD0E9007B4B17 - 29E35EA21A2FD0E9007B4B17 - 29E35E991A2F8DB0007B4B17 - 29E35E9A1A2F8DB0007B4B17 - 29E35E9C1A2F8DBC007B4B17 - 29E35E9D1A2F8DBC007B4B17 - 29A7F3A31A3638DE00CF34F2 - 29A7F3A41A3638DE00CF34F2 - 05E2128419D4DB510098F589 - 05E2128B19D4DB510098F589 - 05E2128C19D4DB510098F589 - - isa - PBXGroup - path - Sample - sourceTree - <group> - - 05E2128419D4DB510098F589 - - children - - 0585427F19D4DBE100606EA6 - 6C2C82AA19EE274300767484 - 6C2C82AB19EE274300767484 - 05E2128519D4DB510098F589 - 05E2128619D4DB510098F589 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - 05E2128519D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - 05E2128619D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - 05E2128719D4DB510098F589 - - fileRef - 05E2128619D4DB510098F589 - isa - PBXBuildFile - - 05E2128819D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - 05E2128919D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - 05E2128A19D4DB510098F589 - - fileRef - 05E2128919D4DB510098F589 - isa - PBXBuildFile - - 05E2128B19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - 05E2128C19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - 05E2128D19D4DB510098F589 - - fileRef - 05E2128C19D4DB510098F589 - isa - PBXBuildFile - - 05E212A219D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_SYMBOLS_PRIVATE_EXTERN - NO - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - - isa - XCBuildConfiguration - name - Debug - - 05E212A319D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - YES - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - 05E212A419D4DB510098F589 - - buildConfigurations - - 05E212A519D4DB510098F589 - 05E212A619D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E212A519D4DB510098F589 - - baseConfigurationReference - 719D9CF4B9A9C5DC6EE4934C - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - - isa - XCBuildConfiguration - name - Debug - - 05E212A619D4DB510098F589 - - baseConfigurationReference - 2D75070E7AB95D12F1F0BAB6 - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - - isa - XCBuildConfiguration - name - Release - - 0B04B566CE0BBB69447C7222 - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - 1A943BF0259746F18D6E423F - - children - - 0B04B566CE0BBB69447C7222 - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - 1AE410B73DA5C3BD087ACDD7 - - children - - 719D9CF4B9A9C5DC6EE4934C - 2D75070E7AB95D12F1F0BAB6 - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 29A7F3A31A3638DE00CF34F2 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - SlowpokeTextNode.h - sourceTree - <group> - - 29A7F3A41A3638DE00CF34F2 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - SlowpokeTextNode.m - sourceTree - <group> - - 29A7F3A51A3638DE00CF34F2 - - fileRef - 29A7F3A41A3638DE00CF34F2 - isa - PBXBuildFile - - 29E35E991A2F8DB0007B4B17 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - SlowpokeImageNode.h - sourceTree - <group> - - 29E35E9A1A2F8DB0007B4B17 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - SlowpokeImageNode.m - sourceTree - <group> - - 29E35E9B1A2F8DB0007B4B17 - - fileRef - 29E35E9A1A2F8DB0007B4B17 - isa - PBXBuildFile - - 29E35E9C1A2F8DBC007B4B17 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - SlowpokeShareNode.h - sourceTree - <group> - - 29E35E9D1A2F8DBC007B4B17 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - SlowpokeShareNode.m - sourceTree - <group> - - 29E35E9E1A2F8DBC007B4B17 - - fileRef - 29E35E9D1A2F8DBC007B4B17 - isa - PBXBuildFile - - 29E35E9F1A2F9650007B4B17 - - isa - PBXFileReference - lastKnownFileType - image.png - path - logo.png - sourceTree - <group> - - 29E35EA01A2F9650007B4B17 - - fileRef - 29E35E9F1A2F9650007B4B17 - isa - PBXBuildFile - - 29E35EA11A2FD0E9007B4B17 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - PostNode.h - sourceTree - <group> - - 29E35EA21A2FD0E9007B4B17 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - PostNode.m - sourceTree - <group> - - 29E35EA31A2FD0E9007B4B17 - - fileRef - 29E35EA21A2FD0E9007B4B17 - isa - PBXBuildFile - - 2D75070E7AB95D12F1F0BAB6 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - 37563A5CF6FA5205CCAB18B2 - - fileRef - 0B04B566CE0BBB69447C7222 - isa - PBXBuildFile - - 6C2C82AA19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-667h@2x.png - sourceTree - SOURCE_ROOT - - 6C2C82AB19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-736h@3x.png - sourceTree - SOURCE_ROOT - - 6C2C82AC19EE274300767484 - - fileRef - 6C2C82AA19EE274300767484 - isa - PBXBuildFile - - 6C2C82AD19EE274300767484 - - fileRef - 6C2C82AB19EE274300767484 - isa - PBXBuildFile - - 719D9CF4B9A9C5DC6EE4934C - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - 8DF84F9CA640D8BF98C61D50 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - E080B80F89C34A25B3488E26 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - F012A6F39E0149F18F564F50 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - - rootObject - 05E2127919D4DB510098F589 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 29A7F3A51A3638DE00CF34F2 /* SlowpokeTextNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A7F3A41A3638DE00CF34F2 /* SlowpokeTextNode.m */; }; + 29E35E9B1A2F8DB0007B4B17 /* SlowpokeImageNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 29E35E9A1A2F8DB0007B4B17 /* SlowpokeImageNode.m */; }; + 29E35E9E1A2F8DBC007B4B17 /* SlowpokeShareNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 29E35E9D1A2F8DBC007B4B17 /* SlowpokeShareNode.m */; }; + 29E35EA01A2F9650007B4B17 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 29E35E9F1A2F9650007B4B17 /* logo.png */; }; + 29E35EA31A2FD0E9007B4B17 /* PostNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 29E35EA21A2FD0E9007B4B17 /* PostNode.m */; }; + 37563A5CF6FA5205CCAB18B2 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B04B566CE0BBB69447C7222 /* libPods-Sample.a */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 0B04B566CE0BBB69447C7222 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 29A7F3A31A3638DE00CF34F2 /* SlowpokeTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SlowpokeTextNode.h; sourceTree = ""; }; + 29A7F3A41A3638DE00CF34F2 /* SlowpokeTextNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SlowpokeTextNode.m; sourceTree = ""; }; + 29E35E991A2F8DB0007B4B17 /* SlowpokeImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SlowpokeImageNode.h; sourceTree = ""; }; + 29E35E9A1A2F8DB0007B4B17 /* SlowpokeImageNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SlowpokeImageNode.m; sourceTree = ""; }; + 29E35E9C1A2F8DBC007B4B17 /* SlowpokeShareNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SlowpokeShareNode.h; sourceTree = ""; }; + 29E35E9D1A2F8DBC007B4B17 /* SlowpokeShareNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SlowpokeShareNode.m; sourceTree = ""; }; + 29E35E9F1A2F9650007B4B17 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = ""; }; + 29E35EA11A2FD0E9007B4B17 /* PostNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PostNode.h; sourceTree = ""; }; + 29E35EA21A2FD0E9007B4B17 /* PostNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostNode.m; sourceTree = ""; }; + 2D75070E7AB95D12F1F0BAB6 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + 719D9CF4B9A9C5DC6EE4934C /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 37563A5CF6FA5205CCAB18B2 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 29E35E9F1A2F9650007B4B17 /* logo.png */, + 29E35EA11A2FD0E9007B4B17 /* PostNode.h */, + 29E35EA21A2FD0E9007B4B17 /* PostNode.m */, + 29E35E991A2F8DB0007B4B17 /* SlowpokeImageNode.h */, + 29E35E9A1A2F8DB0007B4B17 /* SlowpokeImageNode.m */, + 29E35E9C1A2F8DBC007B4B17 /* SlowpokeShareNode.h */, + 29E35E9D1A2F8DBC007B4B17 /* SlowpokeShareNode.m */, + 29A7F3A31A3638DE00CF34F2 /* SlowpokeTextNode.h */, + 29A7F3A41A3638DE00CF34F2 /* SlowpokeTextNode.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0B04B566CE0BBB69447C7222 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + 719D9CF4B9A9C5DC6EE4934C /* Pods-Sample.debug.xcconfig */, + 2D75070E7AB95D12F1F0BAB6 /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + 8DF84F9CA640D8BF98C61D50 /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0600; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 29E35EA01A2F9650007B4B17 /* logo.png in Resources */, + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 8DF84F9CA640D8BF98C61D50 /* 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-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 29A7F3A51A3638DE00CF34F2 /* SlowpokeTextNode.m in Sources */, + 29E35E9B1A2F8DB0007B4B17 /* SlowpokeImageNode.m in Sources */, + 29E35E9E1A2F8DBC007B4B17 /* SlowpokeShareNode.m in Sources */, + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 29E35EA31A2FD0E9007B4B17 /* PostNode.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 719D9CF4B9A9C5DC6EE4934C /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2D75070E7AB95D12F1F0BAB6 /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples_extra/RepoSearcher/Podfile b/examples_extra/RepoSearcher/Podfile new file mode 100644 index 0000000000..2652a3aca1 --- /dev/null +++ b/examples_extra/RepoSearcher/Podfile @@ -0,0 +1,10 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'RepoSearcher' do + # Comment the next line if you're not using Swift and don't want to use dynamic frameworks + use_frameworks! + + # Pods for RepoSearcher + pod 'AsyncDisplayKit/IGListKit', :path => '../..' +end diff --git a/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.pbxproj b/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..2dac7da9fe --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.pbxproj @@ -0,0 +1,422 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 427F7FCA1E58519300D3E11B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427F7FC91E58519300D3E11B /* AppDelegate.swift */; }; + 427F7FCC1E58519300D3E11B /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427F7FCB1E58519300D3E11B /* SearchViewController.swift */; }; + 427F7FD11E58519300D3E11B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 427F7FD01E58519300D3E11B /* Assets.xcassets */; }; + 427F7FDC1E58558C00D3E11B /* LabelSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427F7FDB1E58558C00D3E11B /* LabelSectionController.swift */; }; + 427F7FDE1E58626A00D3E11B /* SearchSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427F7FDD1E58626A00D3E11B /* SearchSectionController.swift */; }; + 427F7FE01E58627B00D3E11B /* SearchNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427F7FDF1E58627B00D3E11B /* SearchNode.swift */; }; + 427F7FE21E58659600D3E11B /* NSObject+IGListDiffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427F7FE11E58659600D3E11B /* NSObject+IGListDiffable.swift */; }; + 427F7FE71E5868BD00D3E11B /* IGListCollectionContext+ASDK.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427F7FE61E5868BD00D3E11B /* IGListCollectionContext+ASDK.swift */; }; + 427F7FED1E5872D200D3E11B /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 427F7FEC1E5872D200D3E11B /* Launch Screen.storyboard */; }; + E222079F2736F3FCAE57814E /* Pods_RepoSearcher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD4426BE878430E4E8B66198 /* Pods_RepoSearcher.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 427F7FC61E58519300D3E11B /* RepoSearcher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RepoSearcher.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 427F7FC91E58519300D3E11B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 427F7FCB1E58519300D3E11B /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; + 427F7FD01E58519300D3E11B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 427F7FD51E58519300D3E11B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 427F7FDB1E58558C00D3E11B /* LabelSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelSectionController.swift; sourceTree = ""; }; + 427F7FDD1E58626A00D3E11B /* SearchSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchSectionController.swift; sourceTree = ""; }; + 427F7FDF1E58627B00D3E11B /* SearchNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchNode.swift; sourceTree = ""; }; + 427F7FE11E58659600D3E11B /* NSObject+IGListDiffable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+IGListDiffable.swift"; sourceTree = ""; }; + 427F7FE61E5868BD00D3E11B /* IGListCollectionContext+ASDK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IGListCollectionContext+ASDK.swift"; sourceTree = ""; }; + 427F7FEC1E5872D200D3E11B /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; + DA53F83B08FF5735C4EAA6A5 /* Pods-RepoSearcher.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RepoSearcher.release.xcconfig"; path = "Pods/Target Support Files/Pods-RepoSearcher/Pods-RepoSearcher.release.xcconfig"; sourceTree = ""; }; + DD4426BE878430E4E8B66198 /* Pods_RepoSearcher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RepoSearcher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E925D85286FDB929874729EE /* Pods-RepoSearcher.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RepoSearcher.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RepoSearcher/Pods-RepoSearcher.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 427F7FC31E58519300D3E11B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E222079F2736F3FCAE57814E /* Pods_RepoSearcher.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 12D677BC1212718498BBD9BF /* Frameworks */ = { + isa = PBXGroup; + children = ( + DD4426BE878430E4E8B66198 /* Pods_RepoSearcher.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 427F7FBD1E58519300D3E11B = { + isa = PBXGroup; + children = ( + 427F7FC81E58519300D3E11B /* RepoSearcher */, + 427F7FC71E58519300D3E11B /* Products */, + 427F7FE91E5869F500D3E11B /* Resources */, + 7EEB49B1AF149712F6D01B0B /* Pods */, + 12D677BC1212718498BBD9BF /* Frameworks */, + ); + sourceTree = ""; + }; + 427F7FC71E58519300D3E11B /* Products */ = { + isa = PBXGroup; + children = ( + 427F7FC61E58519300D3E11B /* RepoSearcher.app */, + ); + name = Products; + sourceTree = ""; + }; + 427F7FC81E58519300D3E11B /* RepoSearcher */ = { + isa = PBXGroup; + children = ( + 427F7FC91E58519300D3E11B /* AppDelegate.swift */, + 427F7FE81E5869EB00D3E11B /* View Controllers */, + 427F7FE41E58664700D3E11B /* Section Controller */, + 427F7FE31E58664000D3E11B /* Nodes */, + 427F7FE51E58666400D3E11B /* Extensions */, + ); + path = RepoSearcher; + sourceTree = ""; + }; + 427F7FE31E58664000D3E11B /* Nodes */ = { + isa = PBXGroup; + children = ( + 427F7FDF1E58627B00D3E11B /* SearchNode.swift */, + ); + name = Nodes; + sourceTree = ""; + }; + 427F7FE41E58664700D3E11B /* Section Controller */ = { + isa = PBXGroup; + children = ( + 427F7FDB1E58558C00D3E11B /* LabelSectionController.swift */, + 427F7FDD1E58626A00D3E11B /* SearchSectionController.swift */, + ); + name = "Section Controller"; + sourceTree = ""; + }; + 427F7FE51E58666400D3E11B /* Extensions */ = { + isa = PBXGroup; + children = ( + 427F7FE11E58659600D3E11B /* NSObject+IGListDiffable.swift */, + 427F7FE61E5868BD00D3E11B /* IGListCollectionContext+ASDK.swift */, + ); + name = Extensions; + sourceTree = ""; + }; + 427F7FE81E5869EB00D3E11B /* View Controllers */ = { + isa = PBXGroup; + children = ( + 427F7FCB1E58519300D3E11B /* SearchViewController.swift */, + ); + name = "View Controllers"; + sourceTree = ""; + }; + 427F7FE91E5869F500D3E11B /* Resources */ = { + isa = PBXGroup; + children = ( + 427F7FD01E58519300D3E11B /* Assets.xcassets */, + 427F7FD51E58519300D3E11B /* Info.plist */, + 427F7FEC1E5872D200D3E11B /* Launch Screen.storyboard */, + ); + name = Resources; + path = RepoSearcher; + sourceTree = ""; + }; + 7EEB49B1AF149712F6D01B0B /* Pods */ = { + isa = PBXGroup; + children = ( + E925D85286FDB929874729EE /* Pods-RepoSearcher.debug.xcconfig */, + DA53F83B08FF5735C4EAA6A5 /* Pods-RepoSearcher.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 427F7FC51E58519300D3E11B /* RepoSearcher */ = { + isa = PBXNativeTarget; + buildConfigurationList = 427F7FD81E58519300D3E11B /* Build configuration list for PBXNativeTarget "RepoSearcher" */; + buildPhases = ( + DF4AE1C3A409D227D336F673 /* [CP] Check Pods Manifest.lock */, + 427F7FC21E58519300D3E11B /* Sources */, + 427F7FC31E58519300D3E11B /* Frameworks */, + 427F7FC41E58519300D3E11B /* Resources */, + D44B2BB927A2C0B1D632AB44 /* [CP] Embed Pods Frameworks */, + D6DD975D2C366D8DF2A87634 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RepoSearcher; + productName = RepoSearcher; + productReference = 427F7FC61E58519300D3E11B /* RepoSearcher.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 427F7FBE1E58519300D3E11B /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0820; + LastUpgradeCheck = 0820; + ORGANIZATIONNAME = "Marvin Nazari"; + TargetAttributes = { + 427F7FC51E58519300D3E11B = { + CreatedOnToolsVersion = 8.2.1; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 427F7FC11E58519300D3E11B /* Build configuration list for PBXProject "RepoSearcher" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 427F7FBD1E58519300D3E11B; + productRefGroup = 427F7FC71E58519300D3E11B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 427F7FC51E58519300D3E11B /* RepoSearcher */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 427F7FC41E58519300D3E11B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 427F7FED1E5872D200D3E11B /* Launch Screen.storyboard in Resources */, + 427F7FD11E58519300D3E11B /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + D44B2BB927A2C0B1D632AB44 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RepoSearcher/Pods-RepoSearcher-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + D6DD975D2C366D8DF2A87634 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RepoSearcher/Pods-RepoSearcher-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + DF4AE1C3A409D227D336F673 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 427F7FC21E58519300D3E11B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 427F7FE21E58659600D3E11B /* NSObject+IGListDiffable.swift in Sources */, + 427F7FCC1E58519300D3E11B /* SearchViewController.swift in Sources */, + 427F7FE01E58627B00D3E11B /* SearchNode.swift in Sources */, + 427F7FE71E5868BD00D3E11B /* IGListCollectionContext+ASDK.swift in Sources */, + 427F7FCA1E58519300D3E11B /* AppDelegate.swift in Sources */, + 427F7FDC1E58558C00D3E11B /* LabelSectionController.swift in Sources */, + 427F7FDE1E58626A00D3E11B /* SearchSectionController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 427F7FD61E58519300D3E11B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 427F7FD71E58519300D3E11B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 427F7FD91E58519300D3E11B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E925D85286FDB929874729EE /* Pods-RepoSearcher.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = RepoSearcher/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = wavio.RepoSearcher; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 427F7FDA1E58519300D3E11B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DA53F83B08FF5735C4EAA6A5 /* Pods-RepoSearcher.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = RepoSearcher/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = wavio.RepoSearcher; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 427F7FC11E58519300D3E11B /* Build configuration list for PBXProject "RepoSearcher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 427F7FD61E58519300D3E11B /* Debug */, + 427F7FD71E58519300D3E11B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 427F7FD81E58519300D3E11B /* Build configuration list for PBXNativeTarget "RepoSearcher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 427F7FD91E58519300D3E11B /* Debug */, + 427F7FDA1E58519300D3E11B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 427F7FBE1E58519300D3E11B /* Project object */; +} diff --git a/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..bd13674404 --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/ASMapNode/Sample.xcworkspace/contents.xcworkspacedata b/examples_extra/RepoSearcher/RepoSearcher.xcworkspace/contents.xcworkspacedata similarity index 78% rename from examples/ASMapNode/Sample.xcworkspace/contents.xcworkspacedata rename to examples_extra/RepoSearcher/RepoSearcher.xcworkspace/contents.xcworkspacedata index 7b5a2f3050..c9f12e24dd 100644 --- a/examples/ASMapNode/Sample.xcworkspace/contents.xcworkspacedata +++ b/examples_extra/RepoSearcher/RepoSearcher.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,7 @@ + location = "group:RepoSearcher.xcodeproj"> diff --git a/examples_extra/RepoSearcher/RepoSearcher/AppDelegate.swift b/examples_extra/RepoSearcher/RepoSearcher/AppDelegate.swift new file mode 100644 index 0000000000..d9e8b19907 --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher/AppDelegate.swift @@ -0,0 +1,27 @@ +// +// AppDelegate.swift +// RepoSearcher +// +// Created by Marvin Nazari on 2017-02-18. +// Copyright © 2017 Marvin Nazari. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? = { + let window = UIWindow(frame: UIScreen.main.bounds) + window.backgroundColor = .white + return window + }() + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + window?.rootViewController = UINavigationController(rootViewController: SearchViewController()) + window?.makeKeyAndVisible() + + return true + } +} + diff --git a/examples_extra/RepoSearcher/RepoSearcher/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples_extra/RepoSearcher/RepoSearcher/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..36d2c80d88 --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples_extra/RepoSearcher/RepoSearcher/IGListCollectionContext+ASDK.swift b/examples_extra/RepoSearcher/RepoSearcher/IGListCollectionContext+ASDK.swift new file mode 100644 index 0000000000..b1c623b651 --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher/IGListCollectionContext+ASDK.swift @@ -0,0 +1,17 @@ +// +// IGListCollectionContext+ASDK.swift +// RepoSearcher +// +// Created by Marvin Nazari on 2017-02-18. +// Copyright © 2017 Marvin Nazari. All rights reserved. +// + +import Foundation +import IGListKit +import AsyncDisplayKit + +extension IGListCollectionContext { + func nodeForItem(at index: Int, sectionController: IGListSectionController) -> ASCellNode? { + return (cellForItem(at: index, sectionController: sectionController) as? _ASCollectionViewCell)?.node + } +} diff --git a/examples_extra/RepoSearcher/RepoSearcher/Info.plist b/examples_extra/RepoSearcher/RepoSearcher/Info.plist new file mode 100644 index 0000000000..0f3cc77a42 --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + Launch Screen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples_extra/RepoSearcher/RepoSearcher/LabelSectionController.swift b/examples_extra/RepoSearcher/RepoSearcher/LabelSectionController.swift new file mode 100644 index 0000000000..040634c934 --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher/LabelSectionController.swift @@ -0,0 +1,44 @@ +// +// LabelSectionController.swift +// RepoSearcher +// +// Created by Marvin Nazari on 2017-02-18. +// Copyright © 2017 Marvin Nazari. All rights reserved. +// + +import Foundation +import AsyncDisplayKit +import IGListKit + +final class LabelSectionController: IGListSectionController, IGListSectionType, ASSectionController { + var object: String? + + func nodeBlockForItem(at index: Int) -> ASCellNodeBlock { + let text = object ?? "" + return { + let node = ASTextCellNode() + node.text = text + return node + } + } + + func numberOfItems() -> Int { + return 1 + } + + func didUpdate(to object: Any) { + self.object = String(describing: object) + } + + func didSelectItem(at index: Int) {} + + //ASDK Replacement + func sizeForItem(at index: Int) -> CGSize { + return ASIGListSectionControllerMethods.sizeForItem(at: index) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + return ASIGListSectionControllerMethods.cellForItem(at: index, sectionController: self) + } +} + diff --git a/examples_extra/RepoSearcher/RepoSearcher/Launch Screen.storyboard b/examples_extra/RepoSearcher/RepoSearcher/Launch Screen.storyboard new file mode 100644 index 0000000000..b90f693902 --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher/Launch Screen.storyboard @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples_extra/RepoSearcher/RepoSearcher/NSObject+IGListDiffable.swift b/examples_extra/RepoSearcher/RepoSearcher/NSObject+IGListDiffable.swift new file mode 100644 index 0000000000..176b484300 --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher/NSObject+IGListDiffable.swift @@ -0,0 +1,18 @@ +// +// NSObject+IGListDiffable.swift +// RepoSearcher +// +// Created by Marvin Nazari on 2017-02-18. +// Copyright © 2017 Marvin Nazari. All rights reserved. +// + +import IGListKit + +extension NSObject: IGListDiffable { + public func diffIdentifier() -> NSObjectProtocol { + return self + } + public func isEqual(toDiffableObject object: IGListDiffable?) -> Bool { + return isEqual(object) + } +} diff --git a/examples_extra/RepoSearcher/RepoSearcher/SearchNode.swift b/examples_extra/RepoSearcher/RepoSearcher/SearchNode.swift new file mode 100644 index 0000000000..440cc3752e --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher/SearchNode.swift @@ -0,0 +1,50 @@ +// +// SearchNode.swift +// RepoSearcher +// +// Created by Marvin Nazari on 2017-02-18. +// Copyright © 2017 Marvin Nazari. All rights reserved. +// + +import Foundation +import AsyncDisplayKit + +class SearchNode: ASCellNode { + var searchBarNode: SearchBarNode + + init(delegate: UISearchBarDelegate?) { + self.searchBarNode = SearchBarNode(delegate: delegate) + super.init() + automaticallyManagesSubnodes = true + } + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + return ASInsetLayoutSpec(insets: .zero, child: searchBarNode) + } +} + +final class SearchBarNode: ASDisplayNode { + + weak var delegate: UISearchBarDelegate? + + init(delegate: UISearchBarDelegate?) { + self.delegate = delegate + super.init(viewBlock: { + UISearchBar() + }, didLoad: nil) + style.preferredSize = CGSize(width: UIScreen.main.bounds.width, height: 44) + } + + var searchBar: UISearchBar { + return view as! UISearchBar + } + + override func didLoad() { + super.didLoad() + searchBar.delegate = delegate + searchBar.searchBarStyle = .minimal + searchBar.tintColor = .black + searchBar.backgroundColor = .white + searchBar.placeholder = "Search" + } +} diff --git a/examples_extra/RepoSearcher/RepoSearcher/SearchSectionController.swift b/examples_extra/RepoSearcher/RepoSearcher/SearchSectionController.swift new file mode 100644 index 0000000000..75738a0c99 --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher/SearchSectionController.swift @@ -0,0 +1,71 @@ +// +// SearchSectionController.swift +// RepoSearcher +// +// Created by Marvin Nazari on 2017-02-18. +// Copyright © 2017 Marvin Nazari. All rights reserved. +// + +import AsyncDisplayKit +import IGListKit + +protocol SearchSectionControllerDelegate: class { + func searchSectionController(_ sectionController: SearchSectionController, didChangeText text: String) +} + +final class SearchSectionController: IGListSectionController, IGListSectionType, ASSectionController { + + weak var delegate: SearchSectionControllerDelegate? + + override init() { + super.init() + scrollDelegate = self + } + + func nodeBlockForItem(at index: Int) -> ASCellNodeBlock { + return { [weak self] in + return SearchNode(delegate: self) + } + } + + func numberOfItems() -> Int { + return 1 + } + + func didUpdate(to object: Any) {} + func didSelectItem(at index: Int) {} + + //ASDK Replacement + func sizeForItem(at index: Int) -> CGSize { + return ASIGListSectionControllerMethods.sizeForItem(at: index) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + return ASIGListSectionControllerMethods.cellForItem(at: index, sectionController: self) + } +} + +extension SearchSectionController: IGListScrollDelegate { + func listAdapter(_ listAdapter: IGListAdapter, didScroll sectionController: IGListSectionController) { + guard let searchNode = collectionContext?.nodeForItem(at: 0, sectionController: self) as? SearchNode else { return } + + let searchBar = searchNode.searchBarNode.searchBar + searchBar.text = "" + searchBar.resignFirstResponder() + } + + func listAdapter(_ listAdapter: IGListAdapter!, willBeginDragging sectionController: IGListSectionController!) {} + func listAdapter(_ listAdapter: IGListAdapter!, didEndDragging sectionController: IGListSectionController!, willDecelerate decelerate: Bool) {} + +} + +extension SearchSectionController: UISearchBarDelegate { + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + delegate?.searchSectionController(self, didChangeText: searchText) + } + + func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { + delegate?.searchSectionController(self, didChangeText: "") + } +} + diff --git a/examples_extra/RepoSearcher/RepoSearcher/SearchViewController.swift b/examples_extra/RepoSearcher/RepoSearcher/SearchViewController.swift new file mode 100644 index 0000000000..35f1c39134 --- /dev/null +++ b/examples_extra/RepoSearcher/RepoSearcher/SearchViewController.swift @@ -0,0 +1,65 @@ +// +// SearchViewController.swift +// RepoSearcher +// +// Created by Marvin Nazari on 2017-02-18. +// Copyright © 2017 Marvin Nazari. All rights reserved. +// + +import UIKit +import AsyncDisplayKit +import IGListKit + +class SearchToken: NSObject {} + +final class SearchViewController: ASViewController { + + lazy var adapter: IGListAdapter = { + return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0) + }() + + let words = ["first", "second", "third", "more", "hi", "others"] + + let searchToken = SearchToken() + var filterString = "" + + init() { + let flowLayout = UICollectionViewFlowLayout() + super.init(node: ASCollectionNode(collectionViewLayout: flowLayout)) + adapter.setASDKCollectionNode(node) + adapter.dataSource = self + title = "Search" + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension SearchViewController: IGListAdapterDataSource { + func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { + if object is SearchToken { + let section = SearchSectionController() + section.delegate = self + return section + } + return LabelSectionController() + } + + func emptyView(for listAdapter: IGListAdapter) -> UIView? { + // emptyView dosent work in this secenario, there is always one section (searchbar) present in collection + return nil + } + + func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { + guard filterString != "" else { return [searchToken] + words.map { $0 as IGListDiffable } } + return [searchToken] + words.filter { $0.lowercased().contains(filterString.lowercased()) }.map { $0 as IGListDiffable } + } +} + +extension SearchViewController: SearchSectionControllerDelegate { + func searchSectionController(_ sectionController: SearchSectionController, didChangeText text: String) { + filterString = text + adapter.performUpdates(animated: true, completion: nil) + } +} diff --git a/examples_extra/Shop/Podfile b/examples_extra/Shop/Podfile new file mode 100644 index 0000000000..c33a398d6b --- /dev/null +++ b/examples_extra/Shop/Podfile @@ -0,0 +1,17 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'Shop' do + # Comment the next line if you're not using Swift and don't want to use dynamic frameworks + # use_frameworks! + + # Pods for Shop + +pod 'AsyncDisplayKit' + + target 'ShopTests' do + inherit! :search_paths + # Pods for testing + end + +end diff --git a/examples_extra/Shop/Screenshots/IMG_0008.jpg b/examples_extra/Shop/Screenshots/IMG_0008.jpg new file mode 100644 index 0000000000..8e0862586d Binary files /dev/null and b/examples_extra/Shop/Screenshots/IMG_0008.jpg differ diff --git a/examples_extra/Shop/Screenshots/IMG_0009.jpg b/examples_extra/Shop/Screenshots/IMG_0009.jpg new file mode 100644 index 0000000000..9e7906cc60 Binary files /dev/null and b/examples_extra/Shop/Screenshots/IMG_0009.jpg differ diff --git a/examples_extra/Shop/Screenshots/IMG_0010.jpg b/examples_extra/Shop/Screenshots/IMG_0010.jpg new file mode 100644 index 0000000000..3d1209299f Binary files /dev/null and b/examples_extra/Shop/Screenshots/IMG_0010.jpg differ diff --git a/examples_extra/Shop/Screenshots/IMG_0011.jpg b/examples_extra/Shop/Screenshots/IMG_0011.jpg new file mode 100644 index 0000000000..717deae6b2 Binary files /dev/null and b/examples_extra/Shop/Screenshots/IMG_0011.jpg differ diff --git a/examples_extra/Shop/Shop.xcodeproj/project.pbxproj b/examples_extra/Shop/Shop.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..cdbdc81693 --- /dev/null +++ b/examples_extra/Shop/Shop.xcodeproj/project.pbxproj @@ -0,0 +1,662 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 27068CAB1DDC5F4400F1A191 /* ProductsLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27068CAA1DDC5F4400F1A191 /* ProductsLayout.swift */; }; + 27120DCB1DD9AB2100123E7E /* ShopCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27120DCA1DD9AB2100123E7E /* ShopCellNode.swift */; }; + 27120DCD1DD9BA1100123E7E /* DummyGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27120DCC1DD9BA1100123E7E /* DummyGenerator.swift */; }; + 278BFA221DD4A7B80065BACA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278BFA211DD4A7B80065BACA /* AppDelegate.swift */; }; + 278BFA291DD4A7B80065BACA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 278BFA281DD4A7B80065BACA /* Assets.xcassets */; }; + 278BFA2C1DD4A7B80065BACA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 278BFA2A1DD4A7B80065BACA /* LaunchScreen.storyboard */; }; + 278BFA371DD4A7B80065BACA /* ShopTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278BFA361DD4A7B80065BACA /* ShopTests.swift */; }; + 278BFA4E1DD4ABE80065BACA /* ShopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278BFA4D1DD4ABE80065BACA /* ShopViewController.swift */; }; + 278BFA501DD4AC0C0065BACA /* ProductViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278BFA4F1DD4AC0C0065BACA /* ProductViewController.swift */; }; + 278BFA581DD4B91E0065BACA /* Product.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278BFA571DD4B91E0065BACA /* Product.swift */; }; + 278BFA5B1DD4BA720065BACA /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278BFA5A1DD4BA720065BACA /* UIColor.swift */; }; + 278BFA5D1DD4BB270065BACA /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278BFA5C1DD4BB270065BACA /* Category.swift */; }; + 279300201DDAFA06000E9596 /* ProductsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2793001F1DDAFA06000E9596 /* ProductsTableViewController.swift */; }; + 279300221DDAFE40000E9596 /* ProductsCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279300211DDAFE40000E9596 /* ProductsCollectionViewController.swift */; }; + 279300241DDAFF5C000E9596 /* ProductNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279300231DDAFF5C000E9596 /* ProductNode.swift */; }; + 279300261DDB0936000E9596 /* ProductTableNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279300251DDB0936000E9596 /* ProductTableNode.swift */; }; + 279300281DDB094C000E9596 /* ProductCollectionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279300271DDB094C000E9596 /* ProductCollectionNode.swift */; }; + 2793002A1DDB0D9D000E9596 /* StarRatingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279300291DDB0D9D000E9596 /* StarRatingNode.swift */; }; + 2793002E1DDB7819000E9596 /* ProductCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2793002D1DDB7819000E9596 /* ProductCellNode.swift */; }; + 8F5717B4BA9D2C52DF152D58 /* libPods-ShopTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B0D288DC1A9E61BEAAD7610A /* libPods-ShopTests.a */; }; + E7640D0D33E7EF7F0CCA9C7E /* libPods-Shop.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DC5B45681E16CC3AB224974 /* libPods-Shop.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 278BFA331DD4A7B80065BACA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 278BFA161DD4A7B80065BACA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 278BFA1D1DD4A7B80065BACA; + remoteInfo = Shop; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 27068CAA1DDC5F4400F1A191 /* ProductsLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductsLayout.swift; sourceTree = ""; }; + 27120DCA1DD9AB2100123E7E /* ShopCellNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShopCellNode.swift; sourceTree = ""; }; + 27120DCC1DD9BA1100123E7E /* DummyGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DummyGenerator.swift; sourceTree = ""; }; + 278BFA1E1DD4A7B80065BACA /* Shop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Shop.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 278BFA211DD4A7B80065BACA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 278BFA281DD4A7B80065BACA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 278BFA2B1DD4A7B80065BACA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 278BFA2D1DD4A7B80065BACA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 278BFA321DD4A7B80065BACA /* ShopTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ShopTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 278BFA361DD4A7B80065BACA /* ShopTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopTests.swift; sourceTree = ""; }; + 278BFA381DD4A7B80065BACA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 278BFA4D1DD4ABE80065BACA /* ShopViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShopViewController.swift; sourceTree = ""; }; + 278BFA4F1DD4AC0C0065BACA /* ProductViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductViewController.swift; sourceTree = ""; }; + 278BFA531DD4ACA20065BACA /* Shop-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Shop-Bridging-Header.h"; sourceTree = ""; }; + 278BFA571DD4B91E0065BACA /* Product.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Product.swift; sourceTree = ""; }; + 278BFA5A1DD4BA720065BACA /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; + 278BFA5C1DD4BB270065BACA /* Category.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; + 2793001F1DDAFA06000E9596 /* ProductsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductsTableViewController.swift; sourceTree = ""; }; + 279300211DDAFE40000E9596 /* ProductsCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductsCollectionViewController.swift; sourceTree = ""; }; + 279300231DDAFF5C000E9596 /* ProductNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductNode.swift; sourceTree = ""; }; + 279300251DDB0936000E9596 /* ProductTableNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductTableNode.swift; sourceTree = ""; }; + 279300271DDB094C000E9596 /* ProductCollectionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductCollectionNode.swift; sourceTree = ""; }; + 279300291DDB0D9D000E9596 /* StarRatingNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarRatingNode.swift; sourceTree = ""; }; + 2793002D1DDB7819000E9596 /* ProductCellNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductCellNode.swift; sourceTree = ""; }; + 5D73AC819C6A1A66142C6E20 /* Pods-ShopTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShopTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ShopTests/Pods-ShopTests.debug.xcconfig"; sourceTree = ""; }; + 6B92A48D8DC2366AED7058DF /* Pods-Shop.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Shop.release.xcconfig"; path = "Pods/Target Support Files/Pods-Shop/Pods-Shop.release.xcconfig"; sourceTree = ""; }; + 9BCAE8CB2E27DCA70B4C59A2 /* Pods-Shop.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Shop.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Shop/Pods-Shop.debug.xcconfig"; sourceTree = ""; }; + 9DC5B45681E16CC3AB224974 /* libPods-Shop.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Shop.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F7CBB92CF3D36E2CB9ADD7E /* Pods-ShopTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShopTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ShopTests/Pods-ShopTests.release.xcconfig"; sourceTree = ""; }; + B0D288DC1A9E61BEAAD7610A /* libPods-ShopTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ShopTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 278BFA1B1DD4A7B80065BACA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E7640D0D33E7EF7F0CCA9C7E /* libPods-Shop.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 278BFA2F1DD4A7B80065BACA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8F5717B4BA9D2C52DF152D58 /* libPods-ShopTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 278BFA151DD4A7B80065BACA = { + isa = PBXGroup; + children = ( + 278BFA201DD4A7B80065BACA /* Shop */, + 278BFA351DD4A7B80065BACA /* ShopTests */, + 278BFA1F1DD4A7B80065BACA /* Products */, + D6E142ADAC3713000502DF84 /* Pods */, + 8B843E1CE2A6C2C590C60825 /* Frameworks */, + ); + sourceTree = ""; + }; + 278BFA1F1DD4A7B80065BACA /* Products */ = { + isa = PBXGroup; + children = ( + 278BFA1E1DD4A7B80065BACA /* Shop.app */, + 278BFA321DD4A7B80065BACA /* ShopTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 278BFA201DD4A7B80065BACA /* Shop */ = { + isa = PBXGroup; + children = ( + 278BFA591DD4BA590065BACA /* Extensions */, + 278BFA491DD4AB880065BACA /* Scenes */, + 278BFA481DD4AB510065BACA /* Models */, + 278BFA211DD4A7B80065BACA /* AppDelegate.swift */, + 278BFA281DD4A7B80065BACA /* Assets.xcassets */, + 278BFA2A1DD4A7B80065BACA /* LaunchScreen.storyboard */, + 278BFA2D1DD4A7B80065BACA /* Info.plist */, + 278BFA531DD4ACA20065BACA /* Shop-Bridging-Header.h */, + ); + path = Shop; + sourceTree = ""; + }; + 278BFA351DD4A7B80065BACA /* ShopTests */ = { + isa = PBXGroup; + children = ( + 278BFA361DD4A7B80065BACA /* ShopTests.swift */, + 278BFA381DD4A7B80065BACA /* Info.plist */, + ); + path = ShopTests; + sourceTree = ""; + }; + 278BFA481DD4AB510065BACA /* Models */ = { + isa = PBXGroup; + children = ( + 278BFA571DD4B91E0065BACA /* Product.swift */, + 278BFA5C1DD4BB270065BACA /* Category.swift */, + 27120DCC1DD9BA1100123E7E /* DummyGenerator.swift */, + ); + path = Models; + sourceTree = ""; + }; + 278BFA491DD4AB880065BACA /* Scenes */ = { + isa = PBXGroup; + children = ( + 278BFA4C1DD4ABA90065BACA /* Product */, + 278BFA4B1DD4AB9F0065BACA /* Products */, + 278BFA4A1DD4AB950065BACA /* Shop */, + ); + path = Scenes; + sourceTree = ""; + }; + 278BFA4A1DD4AB950065BACA /* Shop */ = { + isa = PBXGroup; + children = ( + 278BFA4D1DD4ABE80065BACA /* ShopViewController.swift */, + 27120DCA1DD9AB2100123E7E /* ShopCellNode.swift */, + ); + path = Shop; + sourceTree = ""; + }; + 278BFA4B1DD4AB9F0065BACA /* Products */ = { + isa = PBXGroup; + children = ( + 2793001F1DDAFA06000E9596 /* ProductsTableViewController.swift */, + 279300211DDAFE40000E9596 /* ProductsCollectionViewController.swift */, + 279300251DDB0936000E9596 /* ProductTableNode.swift */, + 279300271DDB094C000E9596 /* ProductCollectionNode.swift */, + 27068CAA1DDC5F4400F1A191 /* ProductsLayout.swift */, + ); + path = Products; + sourceTree = ""; + }; + 278BFA4C1DD4ABA90065BACA /* Product */ = { + isa = PBXGroup; + children = ( + 278BFA4F1DD4AC0C0065BACA /* ProductViewController.swift */, + 279300231DDAFF5C000E9596 /* ProductNode.swift */, + 279300291DDB0D9D000E9596 /* StarRatingNode.swift */, + 2793002D1DDB7819000E9596 /* ProductCellNode.swift */, + ); + path = Product; + sourceTree = ""; + }; + 278BFA591DD4BA590065BACA /* Extensions */ = { + isa = PBXGroup; + children = ( + 278BFA5A1DD4BA720065BACA /* UIColor.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 8B843E1CE2A6C2C590C60825 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 9DC5B45681E16CC3AB224974 /* libPods-Shop.a */, + B0D288DC1A9E61BEAAD7610A /* libPods-ShopTests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + D6E142ADAC3713000502DF84 /* Pods */ = { + isa = PBXGroup; + children = ( + 9BCAE8CB2E27DCA70B4C59A2 /* Pods-Shop.debug.xcconfig */, + 6B92A48D8DC2366AED7058DF /* Pods-Shop.release.xcconfig */, + 5D73AC819C6A1A66142C6E20 /* Pods-ShopTests.debug.xcconfig */, + 9F7CBB92CF3D36E2CB9ADD7E /* Pods-ShopTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 278BFA1D1DD4A7B80065BACA /* Shop */ = { + isa = PBXNativeTarget; + buildConfigurationList = 278BFA3B1DD4A7B80065BACA /* Build configuration list for PBXNativeTarget "Shop" */; + buildPhases = ( + 53B8D1D4907B5E11F1FB4D84 /* [CP] Check Pods Manifest.lock */, + 278BFA1A1DD4A7B80065BACA /* Sources */, + 278BFA1B1DD4A7B80065BACA /* Frameworks */, + 278BFA1C1DD4A7B80065BACA /* Resources */, + 7A9094E163FF7B834F7D5B76 /* [CP] Embed Pods Frameworks */, + 89C7C85237928A7AA1AFD80E /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Shop; + productName = Shop; + productReference = 278BFA1E1DD4A7B80065BACA /* Shop.app */; + productType = "com.apple.product-type.application"; + }; + 278BFA311DD4A7B80065BACA /* ShopTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 278BFA3E1DD4A7B80065BACA /* Build configuration list for PBXNativeTarget "ShopTests" */; + buildPhases = ( + 21A817F76279E69128E48719 /* [CP] Check Pods Manifest.lock */, + 278BFA2E1DD4A7B80065BACA /* Sources */, + 278BFA2F1DD4A7B80065BACA /* Frameworks */, + 278BFA301DD4A7B80065BACA /* Resources */, + 4E2E9451B168505B69D5EA0F /* [CP] Embed Pods Frameworks */, + D97894A2A1E9AC5B6FFB9271 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 278BFA341DD4A7B80065BACA /* PBXTargetDependency */, + ); + name = ShopTests; + productName = ShopTests; + productReference = 278BFA321DD4A7B80065BACA /* ShopTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 278BFA161DD4A7B80065BACA /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0810; + LastUpgradeCheck = 0810; + ORGANIZATIONNAME = Dimitri; + TargetAttributes = { + 278BFA1D1DD4A7B80065BACA = { + CreatedOnToolsVersion = 8.1; + DevelopmentTeam = K3L9PW54G7; + LastSwiftMigration = 0810; + ProvisioningStyle = Automatic; + }; + 278BFA311DD4A7B80065BACA = { + CreatedOnToolsVersion = 8.1; + DevelopmentTeam = K3L9PW54G7; + ProvisioningStyle = Automatic; + TestTargetID = 278BFA1D1DD4A7B80065BACA; + }; + }; + }; + buildConfigurationList = 278BFA191DD4A7B80065BACA /* Build configuration list for PBXProject "Shop" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 278BFA151DD4A7B80065BACA; + productRefGroup = 278BFA1F1DD4A7B80065BACA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 278BFA1D1DD4A7B80065BACA /* Shop */, + 278BFA311DD4A7B80065BACA /* ShopTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 278BFA1C1DD4A7B80065BACA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 278BFA2C1DD4A7B80065BACA /* LaunchScreen.storyboard in Resources */, + 278BFA291DD4A7B80065BACA /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 278BFA301DD4A7B80065BACA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 21A817F76279E69128E48719 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 4E2E9451B168505B69D5EA0F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ShopTests/Pods-ShopTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 53B8D1D4907B5E11F1FB4D84 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 7A9094E163FF7B834F7D5B76 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Shop/Pods-Shop-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 89C7C85237928A7AA1AFD80E /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Shop/Pods-Shop-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + D97894A2A1E9AC5B6FFB9271 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ShopTests/Pods-ShopTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 278BFA1A1DD4A7B80065BACA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2793002A1DDB0D9D000E9596 /* StarRatingNode.swift in Sources */, + 27068CAB1DDC5F4400F1A191 /* ProductsLayout.swift in Sources */, + 278BFA501DD4AC0C0065BACA /* ProductViewController.swift in Sources */, + 27120DCD1DD9BA1100123E7E /* DummyGenerator.swift in Sources */, + 278BFA5B1DD4BA720065BACA /* UIColor.swift in Sources */, + 278BFA4E1DD4ABE80065BACA /* ShopViewController.swift in Sources */, + 279300261DDB0936000E9596 /* ProductTableNode.swift in Sources */, + 279300201DDAFA06000E9596 /* ProductsTableViewController.swift in Sources */, + 2793002E1DDB7819000E9596 /* ProductCellNode.swift in Sources */, + 278BFA581DD4B91E0065BACA /* Product.swift in Sources */, + 279300221DDAFE40000E9596 /* ProductsCollectionViewController.swift in Sources */, + 27120DCB1DD9AB2100123E7E /* ShopCellNode.swift in Sources */, + 279300241DDAFF5C000E9596 /* ProductNode.swift in Sources */, + 278BFA221DD4A7B80065BACA /* AppDelegate.swift in Sources */, + 278BFA5D1DD4BB270065BACA /* Category.swift in Sources */, + 279300281DDB094C000E9596 /* ProductCollectionNode.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 278BFA2E1DD4A7B80065BACA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 278BFA371DD4A7B80065BACA /* ShopTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 278BFA341DD4A7B80065BACA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 278BFA1D1DD4A7B80065BACA /* Shop */; + targetProxy = 278BFA331DD4A7B80065BACA /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 278BFA2A1DD4A7B80065BACA /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 278BFA2B1DD4A7B80065BACA /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 278BFA391DD4A7B80065BACA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 278BFA3A1DD4A7B80065BACA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 278BFA3C1DD4A7B80065BACA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9BCAE8CB2E27DCA70B4C59A2 /* Pods-Shop.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = K3L9PW54G7; + INFOPLIST_FILE = Shop/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.sample.Shop; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Shop/Shop-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 278BFA3D1DD4A7B80065BACA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6B92A48D8DC2366AED7058DF /* Pods-Shop.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = K3L9PW54G7; + INFOPLIST_FILE = Shop/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.sample.Shop; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Shop/Shop-Bridging-Header.h"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + 278BFA3F1DD4A7B80065BACA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5D73AC819C6A1A66142C6E20 /* Pods-ShopTests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = K3L9PW54G7; + INFOPLIST_FILE = ShopTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.sample.ShopTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Shop.app/Shop"; + }; + name = Debug; + }; + 278BFA401DD4A7B80065BACA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9F7CBB92CF3D36E2CB9ADD7E /* Pods-ShopTests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = K3L9PW54G7; + INFOPLIST_FILE = ShopTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.sample.ShopTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Shop.app/Shop"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 278BFA191DD4A7B80065BACA /* Build configuration list for PBXProject "Shop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 278BFA391DD4A7B80065BACA /* Debug */, + 278BFA3A1DD4A7B80065BACA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 278BFA3B1DD4A7B80065BACA /* Build configuration list for PBXNativeTarget "Shop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 278BFA3C1DD4A7B80065BACA /* Debug */, + 278BFA3D1DD4A7B80065BACA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 278BFA3E1DD4A7B80065BACA /* Build configuration list for PBXNativeTarget "ShopTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 278BFA3F1DD4A7B80065BACA /* Debug */, + 278BFA401DD4A7B80065BACA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 278BFA161DD4A7B80065BACA /* Project object */; +} diff --git a/examples_extra/Shop/Shop.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples_extra/Shop/Shop.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..951e8297e2 --- /dev/null +++ b/examples_extra/Shop/Shop.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata b/examples_extra/Shop/Shop.xcworkspace/contents.xcworkspacedata similarity index 80% rename from examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata rename to examples_extra/Shop/Shop.xcworkspace/contents.xcworkspacedata index 7b5a2f3050..a8472b4e8a 100644 --- a/examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata +++ b/examples_extra/Shop/Shop.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,7 @@ + location = "group:Shop.xcodeproj"> diff --git a/examples_extra/Shop/Shop/AppDelegate.swift b/examples_extra/Shop/Shop/AppDelegate.swift new file mode 100644 index 0000000000..0c3c759fe5 --- /dev/null +++ b/examples_extra/Shop/Shop/AppDelegate.swift @@ -0,0 +1,55 @@ +// +// AppDelegate.swift +// Shop +// +// Created by Dimitri on 10/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + + UINavigationBar.appearance().tintColor = UIColor.white + UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName : UIColor.white] + UINavigationBar.appearance().barTintColor = UIColor.primaryBarTintColor() + + self.window = UIWindow(frame: UIScreen.main.bounds) + self.window?.backgroundColor = UIColor.white + self.window?.rootViewController = UINavigationController(rootViewController: ShopViewController()) + self.window?.makeKeyAndVisible() + + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/examples_extra/Shop/Shop/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples_extra/Shop/Shop/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..b8236c6534 --- /dev/null +++ b/examples_extra/Shop/Shop/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,48 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Contents.json b/examples_extra/Shop/Shop/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/examples_extra/Shop/Shop/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/Contents.json b/examples_extra/Shop/Shop/Assets.xcassets/Shop/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/examples_extra/Shop/Shop/Assets.xcassets/Shop/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/Contents.json b/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/Contents.json new file mode 100644 index 0000000000..bf7a318771 --- /dev/null +++ b/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "filled_star.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "filled_star-1.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "filled_star-2.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/filled_star-1.png b/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/filled_star-1.png new file mode 100644 index 0000000000..21884f8fe8 Binary files /dev/null and b/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/filled_star-1.png differ diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/filled_star-2.png b/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/filled_star-2.png new file mode 100644 index 0000000000..21884f8fe8 Binary files /dev/null and b/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/filled_star-2.png differ diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/filled_star.png b/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/filled_star.png new file mode 100644 index 0000000000..21884f8fe8 Binary files /dev/null and b/examples_extra/Shop/Shop/Assets.xcassets/Shop/filled_star.imageset/filled_star.png differ diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/Contents.json b/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/Contents.json new file mode 100644 index 0000000000..70a59709db --- /dev/null +++ b/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "category_placeholder-2.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "category_placeholder-1.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "category_placeholder.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/category_placeholder-1.png b/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/category_placeholder-1.png new file mode 100644 index 0000000000..8dec56ca18 Binary files /dev/null and b/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/category_placeholder-1.png differ diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/category_placeholder-2.png b/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/category_placeholder-2.png new file mode 100644 index 0000000000..8dec56ca18 Binary files /dev/null and b/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/category_placeholder-2.png differ diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/category_placeholder.png b/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/category_placeholder.png new file mode 100644 index 0000000000..8dec56ca18 Binary files /dev/null and b/examples_extra/Shop/Shop/Assets.xcassets/Shop/placeholder.imageset/category_placeholder.png differ diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/Contents.json b/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/Contents.json new file mode 100644 index 0000000000..8a56ccde81 --- /dev/null +++ b/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "unfilled_star.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "unfilled_star-1.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "unfilled_star-2.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/unfilled_star-1.png b/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/unfilled_star-1.png new file mode 100644 index 0000000000..a8c225bf04 Binary files /dev/null and b/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/unfilled_star-1.png differ diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/unfilled_star-2.png b/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/unfilled_star-2.png new file mode 100644 index 0000000000..a8c225bf04 Binary files /dev/null and b/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/unfilled_star-2.png differ diff --git a/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/unfilled_star.png b/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/unfilled_star.png new file mode 100644 index 0000000000..a8c225bf04 Binary files /dev/null and b/examples_extra/Shop/Shop/Assets.xcassets/Shop/unfilled_star.imageset/unfilled_star.png differ diff --git a/examples_extra/Shop/Shop/Base.lproj/LaunchScreen.storyboard b/examples_extra/Shop/Shop/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..fdf3f97d1b --- /dev/null +++ b/examples_extra/Shop/Shop/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples_extra/Shop/Shop/Extensions/UIColor.swift b/examples_extra/Shop/Shop/Extensions/UIColor.swift new file mode 100644 index 0000000000..b00e6715dc --- /dev/null +++ b/examples_extra/Shop/Shop/Extensions/UIColor.swift @@ -0,0 +1,29 @@ +// +// UIColor.swift +// Shop +// +// Created by Dimitri on 10/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import Foundation + +extension UIColor { + + class func primaryBackgroundColor() -> UIColor { + return UIColor.init(red: 237/255, green: 239/255, blue: 242/255, alpha: 1.0) + } + + class func primaryBarTintColor() -> UIColor { + return UIColor.init(red: 57/255, green: 59/255, blue: 63/255, alpha: 1.0) + } + + class func containerBackgroundColor() -> UIColor { + return UIColor.init(red: 255/255, green: 255/255, blue: 255/255, alpha: 1.0) + } + + class func containerBorderColor() -> UIColor { + return UIColor.init(red: 231/255, green: 232/255, blue: 235/255, alpha: 1.0) + } + +} diff --git a/examples_extra/Shop/Shop/Info.plist b/examples_extra/Shop/Shop/Info.plist new file mode 100644 index 0000000000..ce18bd2acf --- /dev/null +++ b/examples_extra/Shop/Shop/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/examples_extra/Shop/Shop/Models/Category.swift b/examples_extra/Shop/Shop/Models/Category.swift new file mode 100644 index 0000000000..e1ed7aa9f6 --- /dev/null +++ b/examples_extra/Shop/Shop/Models/Category.swift @@ -0,0 +1,26 @@ +// +// Category.swift +// Shop +// +// Created by Dimitri on 10/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import Foundation + +struct Category { + + var id: String = UUID().uuidString + var imageURL: String + var numberOfProducts: Int = 0 + var title: String + var products: [Product] + + init(title: String, imageURL: String, products: [Product]) { + self.title = title + self.imageURL = imageURL + self.products = products + self.numberOfProducts = products.count + } + +} diff --git a/examples_extra/Shop/Shop/Models/DummyGenerator.swift b/examples_extra/Shop/Shop/Models/DummyGenerator.swift new file mode 100644 index 0000000000..3cfe4ba7a4 --- /dev/null +++ b/examples_extra/Shop/Shop/Models/DummyGenerator.swift @@ -0,0 +1,160 @@ +// +// DummyGenerator.swift +// Shop +// +// Created by Dimitri on 14/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import Foundation + +class DummyGenerator { + + static let sharedGenerator = DummyGenerator() + + // MARK: - Variables + + private let numberOfCategories = 15 + private let imageURLs = ["https://placebear.com/200/200", + "https://placebear.com/200/250", + "https://placebear.com/250/250", + "https://placebear.com/300/200", + "https://placebear.com/300/250", + "https://placebear.com/300/300", + "https://placebear.com/350/200", + "https://placebear.com/350/250", + "https://placebear.com/350/300"] + + // MARK: - Private initializer + + private init() { + + } + + // MARK: - Generate random categories + + func randomCategories() -> [Category] { + var categories: [Category] = [] + for _ in 0.. [Product] { + var products: [Product] = [] + for _ in 0.. String { + return compose(provider: { word }, count: count, middleSeparator: .Space) + } + + public static var sentence: String { + let numberOfWordsInSentence = Int.random(min: 8, max: 16) + let capitalizeFirstLetterDecorator: (String) -> String = { $0.stringWithCapitalizedFirstLetter } + return compose(provider: { word }, count: numberOfWordsInSentence, middleSeparator: .Space, endSeparator: .Dot, decorator: capitalizeFirstLetterDecorator) + } + + public static func sentences(count: Int) -> String { + return compose(provider: { sentence }, count: count, middleSeparator: .Space) + } + + public static var paragraph: String { + let numberOfSentencesInParagraph = Int.random(min: 4, max: 10) + return sentences(count: numberOfSentencesInParagraph) + } + + public static func paragraphs(count: Int) -> String { + return compose(provider: { paragraph }, count: count, middleSeparator: .NewLine) + } + + public static var title: String { + let numberOfWordsInTitle = Int.random(min: 1, max: 2) + let capitalizeStringDecorator: (String) -> String = { $0.capitalized } + return compose(provider: { word }, count: numberOfWordsInTitle, middleSeparator: .Space, decorator: capitalizeStringDecorator) + } + + private enum Separator: String { + case None = "" + case Space = " " + case Dot = "." + case NewLine = "\n" + } + + private static func compose(provider: () -> String, count: Int, middleSeparator: Separator, endSeparator: Separator = .None, decorator: ((String) -> String)? = nil) -> String { + var composedString = "" + + for index in 0.. Element { + let index = Int(arc4random_uniform(UInt32(self.count))) + return self[index] + } +} + +private extension Int { + static func random(min: Int = 0, max: Int) -> Int { + assert(min >= 0) + assert(min < max) + + return Int(arc4random_uniform(UInt32((max - min) + 1))) + min + } +} + +private extension Array { + var randomElement: Element { + return self[Int.random(max: count - 1)] + } +} + +private extension String { + var stringWithCapitalizedFirstLetter: String { + let firstLetterRange = startIndex.. ASLayoutSpec { + return ASInsetLayoutSpec(insets: UIEdgeInsets.zero, child: self.productNode) + } + +} diff --git a/examples_extra/Shop/Shop/Scenes/Product/ProductNode.swift b/examples_extra/Shop/Shop/Scenes/Product/ProductNode.swift new file mode 100644 index 0000000000..97bcec3aec --- /dev/null +++ b/examples_extra/Shop/Shop/Scenes/Product/ProductNode.swift @@ -0,0 +1,118 @@ +// +// ProductNode.swift +// Shop +// +// Created by Dimitri on 15/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import UIKit + +class ProductNode: ASDisplayNode { + + // MARK: - Variables + + private let imageNode: ASNetworkImageNode + private let titleNode: ASTextNode + private let priceNode: ASTextNode + private let starRatingNode: StarRatingNode + private let reviewsNode: ASTextNode + private let descriptionNode: ASTextNode + + private let product: Product + + // MARK: - Object life cycle + + init(product: Product) { + self.product = product + + imageNode = ASNetworkImageNode() + titleNode = ASTextNode() + starRatingNode = StarRatingNode(rating: product.starRating) + priceNode = ASTextNode() + reviewsNode = ASTextNode() + descriptionNode = ASTextNode() + + super.init() + self.setupNodes() + self.buildNodeHierarchy() + } + + // MARK: - Setup nodes + + private func setupNodes() { + self.setupImageNode() + self.setupTitleNode() + self.setupDescriptionNode() + self.setupPriceNode() + self.setupReviewsNode() + } + + private func setupImageNode() { + self.imageNode.url = URL(string: self.product.imageURL) + self.imageNode.preferredFrameSize = CGSize(width: UIScreen.main.bounds.width, height: 300) + } + + private func setupTitleNode() { + self.titleNode.attributedText = NSAttributedString(string: self.product.title, attributes: self.titleTextAttributes()) + self.titleNode.maximumNumberOfLines = 1 + self.titleNode.truncationMode = .byTruncatingTail + } + + private var titleTextAttributes = { + return [NSForegroundColorAttributeName: UIColor.black, NSFontAttributeName: UIFont.boldSystemFont(ofSize: 16)] + } + + private func setupDescriptionNode() { + self.descriptionNode.attributedText = NSAttributedString(string: self.product.descriptionText, attributes: self.descriptionTextAttributes()) + self.descriptionNode.maximumNumberOfLines = 0 + } + + private var descriptionTextAttributes = { + return [NSForegroundColorAttributeName: UIColor.darkGray, NSFontAttributeName: UIFont.systemFont(ofSize: 14)] + } + + private func setupPriceNode() { + self.priceNode.attributedText = NSAttributedString(string: self.product.currency + " \(self.product.price)", attributes: self.priceTextAttributes()) + } + + private var priceTextAttributes = { + return [NSForegroundColorAttributeName: UIColor.red, NSFontAttributeName: UIFont.boldSystemFont(ofSize: 15)] + } + + private func setupReviewsNode() { + self.reviewsNode.attributedText = NSAttributedString(string: "\(self.product.numberOfReviews) reviews", attributes: self.reviewsTextAttributes()) + } + + private var reviewsTextAttributes = { + return [NSForegroundColorAttributeName: UIColor.lightGray, NSFontAttributeName: UIFont.systemFont(ofSize: 14)] + } + + // MARK: - Build node hierarchy + + private func buildNodeHierarchy() { + self.addSubnode(imageNode) + self.addSubnode(titleNode) + self.addSubnode(descriptionNode) + self.addSubnode(starRatingNode) + self.addSubnode(priceNode) + self.addSubnode(reviewsNode) + } + + // MARK: - Layout + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + let spacer = ASLayoutSpec() + spacer.flexGrow = true + self.titleNode.flexShrink = true + let titlePriceSpec = ASStackLayoutSpec(direction: .horizontal, spacing: 2.0, justifyContent: .start, alignItems: .center, children: [self.titleNode, spacer, self.priceNode]) + titlePriceSpec.alignSelf = .stretch + let starRatingReviewsSpec = ASStackLayoutSpec(direction: .horizontal, spacing: 25.0, justifyContent: .start, alignItems: .center, children: [self.starRatingNode, self.reviewsNode]) + let contentSpec = ASStackLayoutSpec(direction: .vertical, spacing: 8.0, justifyContent: .start, alignItems: .stretch, children: [titlePriceSpec, starRatingReviewsSpec, self.descriptionNode]) + contentSpec.flexShrink = true + let insetSpec = ASInsetLayoutSpec(insets: UIEdgeInsetsMake(12.0, 12.0, 12.0, 12.0), child: contentSpec) + let finalSpec = ASStackLayoutSpec(direction: .vertical, spacing: 5.0, justifyContent: .start, alignItems: .center, children: [self.imageNode, insetSpec]) + return finalSpec + } + +} diff --git a/examples_extra/Shop/Shop/Scenes/Product/ProductViewController.swift b/examples_extra/Shop/Shop/Scenes/Product/ProductViewController.swift new file mode 100644 index 0000000000..6251550848 --- /dev/null +++ b/examples_extra/Shop/Shop/Scenes/Product/ProductViewController.swift @@ -0,0 +1,62 @@ +// +// ProductViewController.swift +// Shop +// +// Created by Dimitri on 10/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import UIKit + +class ProductViewController: ASViewController { + + // MARK: - Variables + + let product: Product + + private var tableNode: ASTableNode { + return node + } + + // MARK: - Object life cycle + + init(product: Product) { + self.product = product + super.init(node: ASTableNode()) + tableNode.delegate = self + tableNode.dataSource = self + tableNode.backgroundColor = UIColor.primaryBackgroundColor() + tableNode.view.separatorStyle = .none + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View life cycle + + override func viewDidLoad() { + super.viewDidLoad() + self.setupTitle() + } + +} + +extension ProductViewController: ASTableDataSource, ASTableDelegate { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 + } + + func tableView(_ tableView: ASTableView, nodeForRowAt indexPath: IndexPath) -> ASCellNode { + let node = ProductCellNode(product: self.product) + return node + } + +} + +extension ProductViewController { + func setupTitle() { + self.title = self.product.title + } +} diff --git a/examples_extra/Shop/Shop/Scenes/Product/StarRatingNode.swift b/examples_extra/Shop/Shop/Scenes/Product/StarRatingNode.swift new file mode 100644 index 0000000000..8556251721 --- /dev/null +++ b/examples_extra/Shop/Shop/Scenes/Product/StarRatingNode.swift @@ -0,0 +1,59 @@ +// +// StarRatingNode.swift +// Shop +// +// Created by Dimitri on 15/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import UIKit + +class StarRatingNode: ASDisplayNode { + + // MARK: - Variable + + private lazy var starSize: CGSize = { + return CGSize(width: 15, height: 15) + }() + + private let rating: Int + + private var starImageNodes: [ASDisplayNode] = [] + + // MARK: - Object life cycle + + init(rating: Int) { + self.rating = rating + super.init() + + self.setupStarNodes() + self.buildNodeHierarchy() + } + + // MARK: - Star nodes setup + + private func setupStarNodes() { + for i in 0..<5 { + let imageNode = ASImageNode() + imageNode.image = i <= self.rating ? UIImage(named: "filled_star") : UIImage(named: "unfilled_star") + imageNode.preferredFrameSize = self.starSize + self.starImageNodes.append(imageNode) + } + } + + // MARK: - Build node hierarchy + + private func buildNodeHierarchy() { + for node in self.starImageNodes { + self.addSubnode(node) + } + } + + // MARK: - Layout + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + let layoutSpec = ASStackLayoutSpec(direction: .horizontal, spacing: 5, justifyContent: .start, alignItems: .stretch, children: self.starImageNodes) + return layoutSpec + } + +} diff --git a/examples_extra/Shop/Shop/Scenes/Products/ProductCollectionNode.swift b/examples_extra/Shop/Shop/Scenes/Products/ProductCollectionNode.swift new file mode 100644 index 0000000000..7b0897811b --- /dev/null +++ b/examples_extra/Shop/Shop/Scenes/Products/ProductCollectionNode.swift @@ -0,0 +1,72 @@ +// +// ProductCollectionNode.swift +// Shop +// +// Created by Dimitri on 15/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import UIKit + +class ProductCollectionNode: ASCellNode { + + // MARK: - Variables + + private let containerNode: ContainerNode + + // MARK: - Object life cycle + + init(product: Product) { + self.containerNode = ContainerNode(node: ProductContentNode(product: product)) + super.init() + self.selectionStyle = .none + self.addSubnode(self.containerNode) + } + + // MARK: - Layout + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + let insets = UIEdgeInsetsMake(2, 2, 2, 2) + return ASInsetLayoutSpec(insets: insets, child: self.containerNode) + } + +} + +class ProductContentNode: ASDisplayNode { + + // MARK: - Variables + + private let imageNode: ASNetworkImageNode + private let titleNode: ASTextNode + private let subtitleNode: ASTextNode + + // MARK: - Object life cycle + + init(product: Product) { + imageNode = ASNetworkImageNode() + imageNode.url = URL(string: product.imageURL) + + titleNode = ASTextNode() + let title = NSAttributedString(string: product.title, attributes: [NSForegroundColorAttributeName: UIColor.white, NSFontAttributeName: UIFont.boldSystemFont(ofSize: 17)]) + titleNode.attributedText = title + + subtitleNode = ASTextNode() + let subtitle = NSAttributedString(string: product.currency + " \(product.price)", attributes: [NSForegroundColorAttributeName: UIColor.white, NSFontAttributeName: UIFont.boldSystemFont(ofSize: 15)]) + subtitleNode.attributedText = subtitle + + super.init() + + self.imageNode.addSubnode(self.titleNode) + self.imageNode.addSubnode(self.subtitleNode) + self.addSubnode(self.imageNode) + } + + // MARK: - Layout + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + let textNodesStack = ASStackLayoutSpec(direction: .vertical, spacing: 5, justifyContent: .end, alignItems: .stretch, children: [self.titleNode, self.subtitleNode]) + let insetStack = ASInsetLayoutSpec(insets: UIEdgeInsetsMake(CGFloat.infinity, 10, 10, 10), child: textNodesStack) + return ASOverlayLayoutSpec(child: self.imageNode, overlay: insetStack) + } + +} diff --git a/examples_extra/Shop/Shop/Scenes/Products/ProductTableNode.swift b/examples_extra/Shop/Shop/Scenes/Products/ProductTableNode.swift new file mode 100644 index 0000000000..45c36e656e --- /dev/null +++ b/examples_extra/Shop/Shop/Scenes/Products/ProductTableNode.swift @@ -0,0 +1,123 @@ +// +// ProductTableNode.swift +// Shop +// +// Created by Dimitri on 15/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import UIKit + +class ProductTableNode: ASCellNode { + + // MARK: - Variables + + private lazy var imageSize: CGSize = { + return CGSize(width: 80, height: 80) + }() + + private let product: Product + + private let imageNode: ASNetworkImageNode + private let titleNode: ASTextNode + private let subtitleNode: ASTextNode + private let starRatingNode: StarRatingNode + private let priceNode: ASTextNode + private let separatorNode: ASDisplayNode + + // MARK: - Object life cycle + + init(product: Product) { + self.product = product + + imageNode = ASNetworkImageNode() + titleNode = ASTextNode() + subtitleNode = ASTextNode() + starRatingNode = StarRatingNode(rating: product.starRating) + priceNode = ASTextNode() + separatorNode = ASDisplayNode() + + super.init() + self.setupNodes() + self.buildNodeHierarchy() + } + + // MARK: - Setup nodes + + private func setupNodes() { + self.setupImageNode() + self.setupTitleNode() + self.setupSubtitleNode() + self.setupPriceNode() + self.setupSeparatorNode() + } + + private func setupImageNode() { + self.imageNode.url = URL(string: self.product.imageURL) + self.imageNode.preferredFrameSize = self.imageSize + } + + private func setupTitleNode() { + self.titleNode.attributedText = NSAttributedString(string: self.product.title, attributes: self.titleTextAttributes()) + self.titleNode.maximumNumberOfLines = 1 + self.titleNode.truncationMode = .byTruncatingTail + } + + private var titleTextAttributes = { + return [NSForegroundColorAttributeName: UIColor.black, NSFontAttributeName: UIFont.boldSystemFont(ofSize: 16)] + } + + private func setupSubtitleNode() { + self.subtitleNode.attributedText = NSAttributedString(string: self.product.descriptionText, attributes: self.subtitleTextAttributes()) + self.subtitleNode.maximumNumberOfLines = 2 + self.subtitleNode.truncationMode = .byTruncatingTail + } + + private var subtitleTextAttributes = { + return [NSForegroundColorAttributeName: UIColor.darkGray, NSFontAttributeName: UIFont.systemFont(ofSize: 14)] + } + + private func setupPriceNode() { + self.priceNode.attributedText = NSAttributedString(string: self.product.currency + " \(self.product.price)", attributes: self.priceTextAttributes()) + } + + private var priceTextAttributes = { + return [NSForegroundColorAttributeName: UIColor.red, NSFontAttributeName: UIFont.boldSystemFont(ofSize: 15)] + } + + private func setupSeparatorNode() { + self.separatorNode.backgroundColor = UIColor.lightGray + } + + // MARK: - Build node hierarchy + + private func buildNodeHierarchy() { + self.addSubnode(imageNode) + self.addSubnode(titleNode) + self.addSubnode(subtitleNode) + self.addSubnode(starRatingNode) + self.addSubnode(priceNode) + self.addSubnode(separatorNode) + } + + // MARK: - Layout + + override func layout() { + super.layout() + let separatorHeight = 1 / UIScreen.main.scale + self.separatorNode.frame = CGRect(x: 0.0, y: 0.0, width: self.calculatedSize.width, height: separatorHeight) + } + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + let spacer = ASLayoutSpec() + spacer.flexGrow = true + self.titleNode.flexShrink = true + let titlePriceSpec = ASStackLayoutSpec(direction: .horizontal, spacing: 2.0, justifyContent: .start, alignItems: .center, children: [self.titleNode, spacer, self.priceNode]) + titlePriceSpec.alignSelf = .stretch + let contentSpec = ASStackLayoutSpec(direction: .vertical, spacing: 4.0, justifyContent: .start, alignItems: .stretch, children: [titlePriceSpec, self.subtitleNode, self.starRatingNode]) + contentSpec.flexShrink = true + let finalSpec = ASStackLayoutSpec(direction: .horizontal, spacing: 10.0, justifyContent: .start, alignItems: .start, children: [self.imageNode, contentSpec]) + return ASInsetLayoutSpec(insets: UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0), child: finalSpec) + } + +} diff --git a/examples_extra/Shop/Shop/Scenes/Products/ProductsCollectionViewController.swift b/examples_extra/Shop/Shop/Scenes/Products/ProductsCollectionViewController.swift new file mode 100644 index 0000000000..39c63ead13 --- /dev/null +++ b/examples_extra/Shop/Shop/Scenes/Products/ProductsCollectionViewController.swift @@ -0,0 +1,69 @@ +// +// ProductsCollectionViewController.swift +// Shop +// +// Created by Dimitri on 15/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import UIKit + +class ProductsCollectionViewController: ASViewController { + + // MARK: - Variables + + var products: [Product] + + private var collectionNode: ASCollectionNode { + return node + } + + // MARK: - Object life cycle + + init(products: [Product]) { + self.products = products + super.init(node: ASCollectionNode(collectionViewLayout: ProductsLayout())) + collectionNode.delegate = self + collectionNode.dataSource = self + collectionNode.backgroundColor = UIColor.primaryBackgroundColor() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View life cycle + + override func viewDidLoad() { + super.viewDidLoad() + self.setupTitle() + } + +} + +extension ProductsCollectionViewController: ASCollectionDataSource, ASCollectionDelegate { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.products.count + } + + func collectionView(_ collectionView: ASCollectionView, nodeForItemAt indexPath: IndexPath) -> ASCellNode { + let product = self.products[indexPath.row] + return ProductCollectionNode(product: product) + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let product = self.products[indexPath.row] + let viewController = ProductViewController(product: product) + self.navigationController?.pushViewController(viewController, animated: true) + } + +} + +extension ProductsCollectionViewController { + + func setupTitle() { + self.title = "Bears" + } + +} diff --git a/examples_extra/Shop/Shop/Scenes/Products/ProductsLayout.swift b/examples_extra/Shop/Shop/Scenes/Products/ProductsLayout.swift new file mode 100644 index 0000000000..8a6054c8c6 --- /dev/null +++ b/examples_extra/Shop/Shop/Scenes/Products/ProductsLayout.swift @@ -0,0 +1,55 @@ +// +// ProductsLayout.swift +// Shop +// +// Created by Dimitri on 16/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import UIKit + +class ProductsLayout: UICollectionViewFlowLayout { + + // MARK: - Variables + + let itemHeight: CGFloat = 220 + let numberOfColumns: CGFloat = 2 + + // MARK: - Object life cycle + + override init() { + super.init() + self.setupLayout() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.setupLayout() + } + + // MARK: - Layout + + private func setupLayout() { + self.minimumInteritemSpacing = 0 + self.minimumLineSpacing = 0 + self.scrollDirection = .vertical + } + + func itemWidth() -> CGFloat { + return (collectionView!.frame.width / numberOfColumns) + } + + override var itemSize: CGSize { + set { + self.itemSize = CGSize(width: itemWidth(), height: itemHeight) + } + get { + return CGSize(width: itemWidth(), height: itemHeight) + } + } + + override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint { + return self.collectionView!.contentOffset + } + +} diff --git a/examples_extra/Shop/Shop/Scenes/Products/ProductsTableViewController.swift b/examples_extra/Shop/Shop/Scenes/Products/ProductsTableViewController.swift new file mode 100644 index 0000000000..af716bf62c --- /dev/null +++ b/examples_extra/Shop/Shop/Scenes/Products/ProductsTableViewController.swift @@ -0,0 +1,78 @@ +// +// ProductsTableViewController.swift +// Shop +// +// Created by Dimitri on 15/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import UIKit + +class ProductsTableViewController: ASViewController { + + // MARK: - Variables + + var products: [Product] + + private var tableNode: ASTableNode { + return node + } + + // MARK: - Object life cycle + + init(products: [Product]) { + self.products = products + super.init(node: ASTableNode()) + tableNode.delegate = self + tableNode.dataSource = self + tableNode.backgroundColor = UIColor.white + tableNode.view.separatorStyle = .none + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View life cycle + + override func viewDidLoad() { + super.viewDidLoad() + self.setupTitle() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if let indexPath = self.tableNode.view.indexPathForSelectedRow { + self.tableNode.view.deselectRow(at: indexPath, animated: true) + } + } + +} + +extension ProductsTableViewController: ASTableDataSource, ASTableDelegate { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.products.count + } + + func tableView(_ tableView: ASTableView, nodeForRowAt indexPath: IndexPath) -> ASCellNode { + let product = self.products[indexPath.row] + let node = ProductTableNode(product: product) + return node + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let product = self.products[indexPath.row] + let viewController = ProductViewController(product: product) + self.navigationController?.pushViewController(viewController, animated: true) + } + +} + +extension ProductsTableViewController { + + func setupTitle() { + self.title = "Bears" + } + +} diff --git a/examples_extra/Shop/Shop/Scenes/Shop/ShopCellNode.swift b/examples_extra/Shop/Shop/Scenes/Shop/ShopCellNode.swift new file mode 100644 index 0000000000..df94f35bdc --- /dev/null +++ b/examples_extra/Shop/Shop/Scenes/Shop/ShopCellNode.swift @@ -0,0 +1,105 @@ +// +// ShopCellNode.swift +// Shop +// +// Created by Dimitri on 14/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import UIKit + +class ShopCellNode: ASCellNode { + + // MARK: - Variables + + private let containerNode: ContainerNode + private let categoryNode: CategoryNode + + // MARK: - Object life cycle + + init(category: Category) { + categoryNode = CategoryNode(category: category) + containerNode = ContainerNode(node: categoryNode) + super.init() + self.selectionStyle = .none + self.addSubnode(self.containerNode) + } + + // MARK: - Layout + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + return ASInsetLayoutSpec(insets: UIEdgeInsetsMake(5, 10, 5, 10), child: self.containerNode) + } + +} + +class ContainerNode: ASDisplayNode { + + // MARK: - Variables + + private let contentNode: ASDisplayNode + + // MARK: - Object life cycle + + init(node: ASDisplayNode) { + contentNode = node + super.init() + self.backgroundColor = UIColor.containerBackgroundColor() + self.addSubnode(self.contentNode) + } + + // MARK: - Node life cycle + + override func didLoad() { + super.didLoad() + self.layer.cornerRadius = 5.0 + self.layer.borderColor = UIColor.containerBorderColor().cgColor + self.layer.borderWidth = 1.0 + } + + // MARK: - Layout + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + return ASInsetLayoutSpec(insets: UIEdgeInsetsMake(8, 8, 8, 8), child: self.contentNode) + } + +} + +class CategoryNode: ASDisplayNode { + + // MARK: - Variables + + private let imageNode: ASNetworkImageNode + private let titleNode: ASTextNode + private let subtitleNode: ASTextNode + + // MARK: - Object life cycle + + init(category: Category) { + imageNode = ASNetworkImageNode() + imageNode.url = URL(string: category.imageURL) + + titleNode = ASTextNode() + let title = NSAttributedString(string: category.title, attributes: [NSForegroundColorAttributeName: UIColor.white, NSFontAttributeName: UIFont.boldSystemFont(ofSize: 17)]) + titleNode.attributedText = title + + subtitleNode = ASTextNode() + let subtitle = NSAttributedString(string: "\(category.numberOfProducts) products", attributes: [NSForegroundColorAttributeName: UIColor.white, NSFontAttributeName: UIFont.boldSystemFont(ofSize: 15)]) + subtitleNode.attributedText = subtitle + + super.init() + + self.imageNode.addSubnode(self.titleNode) + self.imageNode.addSubnode(self.subtitleNode) + self.addSubnode(self.imageNode) + } + + // MARK: - Layout + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + let textNodesStack = ASStackLayoutSpec(direction: .vertical, spacing: 5, justifyContent: .end, alignItems: .stretch, children: [self.titleNode, self.subtitleNode]) + let insetStack = ASInsetLayoutSpec(insets: UIEdgeInsetsMake(CGFloat.infinity, 10, 10, 10), child: textNodesStack) + return ASOverlayLayoutSpec(child: self.imageNode, overlay: insetStack) + } + +} diff --git a/examples_extra/Shop/Shop/Scenes/Shop/ShopViewController.swift b/examples_extra/Shop/Shop/Scenes/Shop/ShopViewController.swift new file mode 100644 index 0000000000..ad531709ef --- /dev/null +++ b/examples_extra/Shop/Shop/Scenes/Shop/ShopViewController.swift @@ -0,0 +1,91 @@ +// +// ShopViewController.swift +// Shop +// +// Created by Dimitri on 10/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import UIKit + +class ShopViewController: ASViewController { + + // MARK: - Variables + + lazy var categories: [Category] = { + return DummyGenerator.sharedGenerator.randomCategories() + }() + + private var tableNode: ASTableNode { + return node + } + + // MARK: - Object life cycle + + init() { + super.init(node: ASTableNode()) + tableNode.delegate = self + tableNode.dataSource = self + tableNode.backgroundColor = UIColor.primaryBackgroundColor() + tableNode.view.separatorStyle = .none + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View life cycle + + override func viewDidLoad() { + super.viewDidLoad() + self.setupTitle() + } + +} + +extension ShopViewController: ASTableDataSource, ASTableDelegate { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.categories.count + } + + func tableView(_ tableView: ASTableView, nodeForRowAt indexPath: IndexPath) -> ASCellNode { + let category = self.categories[indexPath.row] + let node = ShopCellNode(category: category) + return node + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let products = self.categories[indexPath.row].products + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + let tableViewAction = UIAlertAction(title: "ASTableNode", style: .default, handler: { (action) in + let viewController = ProductsTableViewController(products: products) + self.navigationController?.pushViewController(viewController, animated: true) + }) + let collectionViewAction = UIAlertAction(title: "ASCollectionNode", style: .default, handler: { (action) in + let viewController = ProductsCollectionViewController(products: products) + self.navigationController?.pushViewController(viewController, animated: true) + }) + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(tableViewAction) + alertController.addAction(collectionViewAction) + alertController.addAction(cancelAction) + DispatchQueue.main.async { + self.present(alertController, animated: true, completion: nil) + } + } + + func tableView(_ tableView: ASTableView, constrainedSizeForRowAt indexPath: IndexPath) -> ASSizeRange { + let width = UIScreen.main.bounds.width + return ASSizeRangeMakeExactSize(CGSize(width: width, height: 175)) + } + +} + +extension ShopViewController { + + func setupTitle() { + self.title = "Bear Shop" + } + +} diff --git a/examples_extra/Shop/Shop/Shop-Bridging-Header.h b/examples_extra/Shop/Shop/Shop-Bridging-Header.h new file mode 100644 index 0000000000..ba84a99573 --- /dev/null +++ b/examples_extra/Shop/Shop/Shop-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import diff --git a/examples_extra/Shop/ShopTests/Info.plist b/examples_extra/Shop/ShopTests/Info.plist new file mode 100644 index 0000000000..6c6c23c43a --- /dev/null +++ b/examples_extra/Shop/ShopTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/examples_extra/Shop/ShopTests/ShopTests.swift b/examples_extra/Shop/ShopTests/ShopTests.swift new file mode 100644 index 0000000000..4838065106 --- /dev/null +++ b/examples_extra/Shop/ShopTests/ShopTests.swift @@ -0,0 +1,36 @@ +// +// ShopTests.swift +// ShopTests +// +// Created by Dimitri on 10/11/2016. +// Copyright © 2016 Dimitri. All rights reserved. +// + +import XCTest +@testable import Shop + +class ShopTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/examples_extra/SynchronousConcurrency/Podfile b/examples_extra/SynchronousConcurrency/Podfile index 919de4b311..defaf55058 100644 --- a/examples_extra/SynchronousConcurrency/Podfile +++ b/examples_extra/SynchronousConcurrency/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj b/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj index 74034a24f4..80d3e6e37e 100644 --- a/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj @@ -1,806 +1,375 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 0342F7A1563F38A62746D4B8 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - 0585427F19D4DBE100606EA6 - - isa - PBXFileReference - lastKnownFileType - image.png - name - Default-568h@2x.png - path - ../Default-568h@2x.png - sourceTree - <group> - - 0585428019D4DBE100606EA6 - - fileRef - 0585427F19D4DBE100606EA6 - isa - PBXBuildFile - - 05E2127819D4DB510098F589 - - children - - 05E2128319D4DB510098F589 - 05E2128219D4DB510098F589 - 1A943BF0259746F18D6E423F - 1AE410B73DA5C3BD087ACDD7 - - indentWidth - 2 - isa - PBXGroup - sourceTree - <group> - tabWidth - 2 - usesTabs - 0 - - 05E2127919D4DB510098F589 - - attributes - - LastUpgradeCheck - 0600 - ORGANIZATIONNAME - Facebook - TargetAttributes - - 05E2128019D4DB510098F589 - - CreatedOnToolsVersion - 6.0.1 - - - - buildConfigurationList - 05E2127C19D4DB510098F589 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - 05E2127819D4DB510098F589 - productRefGroup - 05E2128219D4DB510098F589 - projectDirPath - - projectReferences - - projectRoot - - targets - - 05E2128019D4DB510098F589 - - - 05E2127C19D4DB510098F589 - - buildConfigurations - - 05E212A219D4DB510098F589 - 05E212A319D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E2127D19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 18C2ED861B9B8CE700F627B3 - 18748FDB1BB727B20053A9C1 - 05E2128D19D4DB510098F589 - 05E2128A19D4DB510098F589 - 05E2128719D4DB510098F589 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127E19D4DB510098F589 - - buildActionMask - 2147483647 - files - - C3B2A32888B988D317F5DDE1 - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127F19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 0585428019D4DBE100606EA6 - 6C2C82AC19EE274300767484 - 6C2C82AD19EE274300767484 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2128019D4DB510098F589 - - buildConfigurationList - 05E212A419D4DB510098F589 - buildPhases - - E080B80F89C34A25B3488E26 - 05E2127D19D4DB510098F589 - 05E2127E19D4DB510098F589 - 05E2127F19D4DB510098F589 - F012A6F39E0149F18F564F50 - 0342F7A1563F38A62746D4B8 - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - 05E2128119D4DB510098F589 - productType - com.apple.product-type.application - - 05E2128119D4DB510098F589 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - 05E2128219D4DB510098F589 - - children - - 05E2128119D4DB510098F589 - - isa - PBXGroup - name - Products - sourceTree - <group> - - 05E2128319D4DB510098F589 - - children - - 05E2128819D4DB510098F589 - 05E2128919D4DB510098F589 - 05E2128B19D4DB510098F589 - 05E2128C19D4DB510098F589 - 18748FD91BB727B20053A9C1 - 18748FDA1BB727B20053A9C1 - 18C2ED841B9B8CE700F627B3 - 18C2ED851B9B8CE700F627B3 - 05E2128419D4DB510098F589 - - isa - PBXGroup - path - Sample - sourceTree - <group> - - 05E2128419D4DB510098F589 - - children - - 0585427F19D4DBE100606EA6 - 6C2C82AA19EE274300767484 - 6C2C82AB19EE274300767484 - 05E2128519D4DB510098F589 - 05E2128619D4DB510098F589 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - 05E2128519D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - 05E2128619D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - 05E2128719D4DB510098F589 - - fileRef - 05E2128619D4DB510098F589 - isa - PBXBuildFile - - 05E2128819D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - 05E2128919D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - 05E2128A19D4DB510098F589 - - fileRef - 05E2128919D4DB510098F589 - isa - PBXBuildFile - - 05E2128B19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AsyncTableViewController.h - sourceTree - <group> - - 05E2128C19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AsyncTableViewController.m - sourceTree - <group> - - 05E2128D19D4DB510098F589 - - fileRef - 05E2128C19D4DB510098F589 - isa - PBXBuildFile - - 05E212A219D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_SYMBOLS_PRIVATE_EXTERN - NO - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - - isa - XCBuildConfiguration - name - Debug - - 05E212A319D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - YES - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - 05E212A419D4DB510098F589 - - buildConfigurations - - 05E212A519D4DB510098F589 - 05E212A619D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E212A519D4DB510098F589 - - baseConfigurationReference - 86D5AE7D8306374F99D2E0F7 - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Debug - - 05E212A619D4DB510098F589 - - baseConfigurationReference - 3673DB8C60BCB89039CAD924 - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Release - - 18748FD91BB727B20053A9C1 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AsyncViewController.h - sourceTree - <group> - - 18748FDA1BB727B20053A9C1 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AsyncViewController.m - sourceTree - <group> - - 18748FDB1BB727B20053A9C1 - - fileRef - 18748FDA1BB727B20053A9C1 - isa - PBXBuildFile - - 18C2ED841B9B8CE700F627B3 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - RandomCoreGraphicsNode.h - sourceTree - <group> - - 18C2ED851B9B8CE700F627B3 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - RandomCoreGraphicsNode.m - sourceTree - <group> - - 18C2ED861B9B8CE700F627B3 - - fileRef - 18C2ED851B9B8CE700F627B3 - isa - PBXBuildFile - - 1A943BF0259746F18D6E423F - - children - - 7D3384C58256708C51C64523 - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - 1AE410B73DA5C3BD087ACDD7 - - children - - 86D5AE7D8306374F99D2E0F7 - 3673DB8C60BCB89039CAD924 - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 3673DB8C60BCB89039CAD924 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - 6C2C82AA19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-667h@2x.png - sourceTree - SOURCE_ROOT - - 6C2C82AB19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-736h@3x.png - sourceTree - SOURCE_ROOT - - 6C2C82AC19EE274300767484 - - fileRef - 6C2C82AA19EE274300767484 - isa - PBXBuildFile - - 6C2C82AD19EE274300767484 - - fileRef - 6C2C82AB19EE274300767484 - isa - PBXBuildFile - - 7D3384C58256708C51C64523 - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - 86D5AE7D8306374F99D2E0F7 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - C3B2A32888B988D317F5DDE1 - - fileRef - 7D3384C58256708C51C64523 - isa - PBXBuildFile - - E080B80F89C34A25B3488E26 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - F012A6F39E0149F18F564F50 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - - rootObject - 05E2127919D4DB510098F589 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* AsyncTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* AsyncTableViewController.m */; }; + 18748FDB1BB727B20053A9C1 /* AsyncViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 18748FDA1BB727B20053A9C1 /* AsyncViewController.m */; }; + 18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; + C3B2A32888B988D317F5DDE1 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D3384C58256708C51C64523 /* libPods-Sample.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* AsyncTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncTableViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* AsyncTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AsyncTableViewController.m; sourceTree = ""; }; + 18748FD91BB727B20053A9C1 /* AsyncViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncViewController.h; sourceTree = ""; }; + 18748FDA1BB727B20053A9C1 /* AsyncViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AsyncViewController.m; sourceTree = ""; }; + 18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RandomCoreGraphicsNode.h; sourceTree = ""; }; + 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RandomCoreGraphicsNode.m; sourceTree = ""; }; + 3673DB8C60BCB89039CAD924 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + 7D3384C58256708C51C64523 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 86D5AE7D8306374F99D2E0F7 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C3B2A32888B988D317F5DDE1 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* AsyncTableViewController.h */, + 05E2128C19D4DB510098F589 /* AsyncTableViewController.m */, + 18748FD91BB727B20053A9C1 /* AsyncViewController.h */, + 18748FDA1BB727B20053A9C1 /* AsyncViewController.m */, + 18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */, + 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7D3384C58256708C51C64523 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + 86D5AE7D8306374F99D2E0F7 /* Pods-Sample.debug.xcconfig */, + 3673DB8C60BCB89039CAD924 /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + 0342F7A1563F38A62746D4B8 /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0600; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0342F7A1563F38A62746D4B8 /* 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-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */, + 18748FDB1BB727B20053A9C1 /* AsyncViewController.m in Sources */, + 05E2128D19D4DB510098F589 /* AsyncTableViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 86D5AE7D8306374F99D2E0F7 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3673DB8C60BCB89039CAD924 /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples_extra/SynchronousKittens/Podfile b/examples_extra/SynchronousKittens/Podfile index 919de4b311..defaf55058 100644 --- a/examples_extra/SynchronousKittens/Podfile +++ b/examples_extra/SynchronousKittens/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj b/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj index 549bd30cbe..e7ecd2fc26 100644 --- a/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj @@ -1,806 +1,375 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 05561CF819D4E77700CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - BlurbNode.h - sourceTree - <group> - - 05561CF919D4E77700CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - BlurbNode.m - sourceTree - <group> - - 05561CFA19D4E77700CBA93C - - fileRef - 05561CF919D4E77700CBA93C - isa - PBXBuildFile - - 05561CFB19D4F94A00CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - KittenNode.h - sourceTree - <group> - - 05561CFC19D4F94A00CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.cpp.objcpp - path - KittenNode.mm - sourceTree - <group> - - 05561CFD19D4F94A00CBA93C - - fileRef - 05561CFC19D4F94A00CBA93C - isa - PBXBuildFile - - 0585427F19D4DBE100606EA6 - - isa - PBXFileReference - lastKnownFileType - image.png - name - Default-568h@2x.png - path - ../Default-568h@2x.png - sourceTree - <group> - - 0585428019D4DBE100606EA6 - - fileRef - 0585427F19D4DBE100606EA6 - isa - PBXBuildFile - - 05E2127819D4DB510098F589 - - children - - 05E2128319D4DB510098F589 - 05E2128219D4DB510098F589 - 1A943BF0259746F18D6E423F - 1AE410B73DA5C3BD087ACDD7 - - indentWidth - 2 - isa - PBXGroup - sourceTree - <group> - tabWidth - 2 - usesTabs - 0 - - 05E2127919D4DB510098F589 - - attributes - - LastUpgradeCheck - 0600 - ORGANIZATIONNAME - Facebook - TargetAttributes - - 05E2128019D4DB510098F589 - - CreatedOnToolsVersion - 6.0.1 - - - - buildConfigurationList - 05E2127C19D4DB510098F589 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - 05E2127819D4DB510098F589 - productRefGroup - 05E2128219D4DB510098F589 - projectDirPath - - projectReferences - - projectRoot - - targets - - 05E2128019D4DB510098F589 - - - 05E2127C19D4DB510098F589 - - buildConfigurations - - 05E212A219D4DB510098F589 - 05E212A319D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E2127D19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 05561CFD19D4F94A00CBA93C - 05E2128D19D4DB510098F589 - 05E2128A19D4DB510098F589 - 05561CFA19D4E77700CBA93C - 05E2128719D4DB510098F589 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127E19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 4186058E3E168D53D99777F3 - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127F19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 0585428019D4DBE100606EA6 - 6C2C82AC19EE274300767484 - 6C2C82AD19EE274300767484 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2128019D4DB510098F589 - - buildConfigurationList - 05E212A419D4DB510098F589 - buildPhases - - E080B80F89C34A25B3488E26 - 05E2127D19D4DB510098F589 - 05E2127E19D4DB510098F589 - 05E2127F19D4DB510098F589 - F012A6F39E0149F18F564F50 - 626F666C417D1641EB1FF73D - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - 05E2128119D4DB510098F589 - productType - com.apple.product-type.application - - 05E2128119D4DB510098F589 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - 05E2128219D4DB510098F589 - - children - - 05E2128119D4DB510098F589 - - isa - PBXGroup - name - Products - sourceTree - <group> - - 05E2128319D4DB510098F589 - - children - - 05E2128819D4DB510098F589 - 05E2128919D4DB510098F589 - 05E2128B19D4DB510098F589 - 05E2128C19D4DB510098F589 - 05561CFB19D4F94A00CBA93C - 05561CFC19D4F94A00CBA93C - 05561CF819D4E77700CBA93C - 05561CF919D4E77700CBA93C - 05E2128419D4DB510098F589 - - isa - PBXGroup - path - Sample - sourceTree - <group> - - 05E2128419D4DB510098F589 - - children - - 0585427F19D4DBE100606EA6 - 6C2C82AA19EE274300767484 - 6C2C82AB19EE274300767484 - 05E2128519D4DB510098F589 - 05E2128619D4DB510098F589 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - 05E2128519D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - 05E2128619D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - 05E2128719D4DB510098F589 - - fileRef - 05E2128619D4DB510098F589 - isa - PBXBuildFile - - 05E2128819D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - 05E2128919D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - 05E2128A19D4DB510098F589 - - fileRef - 05E2128919D4DB510098F589 - isa - PBXBuildFile - - 05E2128B19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - 05E2128C19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - 05E2128D19D4DB510098F589 - - fileRef - 05E2128C19D4DB510098F589 - isa - PBXBuildFile - - 05E212A219D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_SYMBOLS_PRIVATE_EXTERN - NO - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - - isa - XCBuildConfiguration - name - Debug - - 05E212A319D4DB510098F589 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - YES - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - 05E212A419D4DB510098F589 - - buildConfigurations - - 05E212A519D4DB510098F589 - 05E212A619D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E212A519D4DB510098F589 - - baseConfigurationReference - BE330B4179344E0F8E899043 - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Debug - - 05E212A619D4DB510098F589 - - baseConfigurationReference - F51793F3B0AD498E5C28A426 - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Release - - 1A943BF0259746F18D6E423F - - children - - 77BD0D94BEDD0C95E94180C7 - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - 1AE410B73DA5C3BD087ACDD7 - - children - - BE330B4179344E0F8E899043 - F51793F3B0AD498E5C28A426 - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 4186058E3E168D53D99777F3 - - fileRef - 77BD0D94BEDD0C95E94180C7 - isa - PBXBuildFile - - 626F666C417D1641EB1FF73D - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - 6C2C82AA19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-667h@2x.png - sourceTree - SOURCE_ROOT - - 6C2C82AB19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-736h@3x.png - sourceTree - SOURCE_ROOT - - 6C2C82AC19EE274300767484 - - fileRef - 6C2C82AA19EE274300767484 - isa - PBXBuildFile - - 6C2C82AD19EE274300767484 - - fileRef - 6C2C82AB19EE274300767484 - isa - PBXBuildFile - - 77BD0D94BEDD0C95E94180C7 - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - BE330B4179344E0F8E899043 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - E080B80F89C34A25B3488E26 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - F012A6F39E0149F18F564F50 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - F51793F3B0AD498E5C28A426 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - - rootObject - 05E2127919D4DB510098F589 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 05561CFA19D4E77700CBA93C /* BlurbNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 05561CF919D4E77700CBA93C /* BlurbNode.m */; }; + 05561CFD19D4F94A00CBA93C /* KittenNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05561CFC19D4F94A00CBA93C /* KittenNode.mm */; }; + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 4186058E3E168D53D99777F3 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 77BD0D94BEDD0C95E94180C7 /* libPods-Sample.a */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 05561CF819D4E77700CBA93C /* BlurbNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlurbNode.h; sourceTree = ""; }; + 05561CF919D4E77700CBA93C /* BlurbNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlurbNode.m; sourceTree = ""; }; + 05561CFB19D4F94A00CBA93C /* KittenNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KittenNode.h; sourceTree = ""; }; + 05561CFC19D4F94A00CBA93C /* KittenNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KittenNode.mm; sourceTree = ""; }; + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + 77BD0D94BEDD0C95E94180C7 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + BE330B4179344E0F8E899043 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + F51793F3B0AD498E5C28A426 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4186058E3E168D53D99777F3 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + 05561CFB19D4F94A00CBA93C /* KittenNode.h */, + 05561CFC19D4F94A00CBA93C /* KittenNode.mm */, + 05561CF819D4E77700CBA93C /* BlurbNode.h */, + 05561CF919D4E77700CBA93C /* BlurbNode.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 77BD0D94BEDD0C95E94180C7 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + BE330B4179344E0F8E899043 /* Pods-Sample.debug.xcconfig */, + F51793F3B0AD498E5C28A426 /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + 626F666C417D1641EB1FF73D /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0600; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 626F666C417D1641EB1FF73D /* 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-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 05561CFD19D4F94A00CBA93C /* KittenNode.mm in Sources */, + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05561CFA19D4E77700CBA93C /* BlurbNode.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BE330B4179344E0F8E899043 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F51793F3B0AD498E5C28A426 /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples_extra/TextStressTest/Podfile b/examples_extra/TextStressTest/Podfile index 5c30ce798e..7a8d8c1a00 100644 --- a/examples_extra/TextStressTest/Podfile +++ b/examples_extra/TextStressTest/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples_extra/VideoTableView/Podfile b/examples_extra/VideoTableView/Podfile index 919de4b311..defaf55058 100644 --- a/examples_extra/VideoTableView/Podfile +++ b/examples_extra/VideoTableView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' +platform :ios, '8.0' target 'Sample' do pod 'AsyncDisplayKit', :path => '../..' end diff --git a/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj b/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj index f579b25bce..8bf0789b10 100644 --- a/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj @@ -330,7 +330,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -343,7 +342,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/inferScript.sh b/inferScript.sh deleted file mode 100755 index 81919c1e81..0000000000 --- a/inferScript.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -if ! [ -x "$(command -v infer)" ]; then - echo "infer not found" - echo "Install infer with homebrew: brew install infer" -else - infer --continue --reactive -- xcodebuild build -workspace AsyncDisplayKit.xcworkspace -scheme "AsyncDisplayKit-iOS" -configuration Debug -sdk iphonesimulator9.3 -fi diff --git a/plans/section-infos-api/Overview.md b/plans/section-infos-api/Overview.md deleted file mode 100644 index 485c86d587..0000000000 --- a/plans/section-infos-api/Overview.md +++ /dev/null @@ -1,34 +0,0 @@ -# Overview - -There is an established pattern where UICollectionViewLayout talks directly to the collection view's delegate to get additional layout info e.g. the size of a header in a given section. - -This pattern is established by Apple's flow layout, and it is used by Pinterest and I'm sure others. It is dangerous when used with ASDK because we update asynchronously, so -for instance if you delete a section from your data source, the layout won't find out until later and in the meantime it may ask the delegate about a section that doesn't exist! - -The solution is to capture this kind of information from the data source immediately when a section is inserted, and make it available to the layout as we update the UICollectionView so that everyone is on the same page. - -Enter: ASSectionUserInfo - -Internally, we use a private object ASCollectionSection to represent one version of a section of items and supplementaries. If the user wants, they can provide us with an ASSectionUserInfo object to accompany the section, which will be read synchronously when the section is inserted. - -## Usage During Layout - -The collection view will make these section infos available in the same way that finished nodes are currently available. - -#### [Sequence Diagram:][diag-layout-usage] - -![][image-layout-usage] - -## Creation When Inserting Sections - -The section infos for any inserted/reloaded sections are queried synchronously, before the node blocks for their items. The top part of this diagram is the same as the current behavior but I think it's useful info =) - -#### [Sequence Diagram:][diag-inserting-sections] - -![][image-inserting-sections] - - -[diag-inserting-sections]: https://www.websequencediagrams.com/?lz=dGl0bGUgSW5zZXJ0aW5nL1JlbG9hZGluZyBTZWN0aW9ucwoKRGF0YSBTb3VyY2UtPkNWOiBpACoFABkIQXRJbmRleGVzOgpDVi0-Q2hhbmdlU2V0IAAzBUNvbnRyb2xsZXIAHRsAURFlbmRVcGRhdGVzADQgAB4MAGIYLT4AehFiZWdpbgAFNACBYxlsb29wIGZvciBlYWNoIHMAgjgGIGluZGV4CiAgIACBAhJDVjoAHwhJbmZvAIJABzoAKAVDVgCBLQcAgnEGAA8aAIMQDACDFwZSZXR1cm4gaWQ8QVMAgz0HVXNlckluZm8-AFQIAIF-EwAWIQCBUg5pdGVtIGluAIFgCACBXQUAgU0Zbm9kZUJsb2NrAIQkB1BhdGgAgWIGAIFXFQARHgCBWRlub2RlIGJsb2NrAE8MAIFRGgAhD2VuZAplbmQAhBktAIUcCwCEYxAAhW0cAIUPGwCGWwYKAIMaCgCDeQgKCg&s=napkin -[image-inserting-sections]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgSW5zZXJ0aW5nL1JlbG9hZGluZyBTZWN0aW9ucwoKRGF0YSBTb3VyY2UtPkNWOiBpACoFABkIQXRJbmRleGVzOgpDVi0-Q2hhbmdlU2V0IAAzBUNvbnRyb2xsZXIAHRsAURFlbmRVcGRhdGVzADQgAB4MAGIYLT4AehFiZWdpbgAFNACBYxlsb29wIGZvciBlYWNoIHMAgjgGIGluZGV4CiAgIACBAhJDVjoAHwhJbmZvAIJABzoAKAVDVgCBLQcAgnEGAA8aAIMQDACDFwZSZXR1cm4gaWQ8QVMAgz0HVXNlckluZm8-AFQIAIF-EwAWIQCBUg5pdGVtIGluAIFgCACBXQUAgU0Zbm9kZUJsb2NrAIQkB1BhdGgAgWIGAIFXFQARHgCBWRlub2RlIGJsb2NrAE8MAIFRGgAhD2VuZAplbmQAhBktAIUcCwCEYxAAhW0cAIUPGwCGWwYKAIMaCgCDeQgKCg&s=napkin -[image-layout-usage]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgcHJlcGFyZUxheW91dCAtIHNlY3Rpb24gbWV0cmljcwoKQ1YtPgAYBjoAHw4KbG9vcCBmb3IgZWFjaAAxCAogICAgCiAgICAATQYtPkNWOgBOCEluZm9BdEluZGV4OgAkBUNWLQBUClJldHVybiBQSU1hc29ucnlTACsKCgBFDQAOFDogY29sdW1uQ291bnQAgQAFADQUAFwSACYQAEYed2lkdGhPZkMAZgUAgT0NAEgmADsFAIIRDQCCUwhVc2UAgmsJZW5kCgoAgjgHAII6BihPSykK&s=napkin -[diag-layout-usage]: https://www.websequencediagrams.com/?lz=dGl0bGUgcHJlcGFyZUxheW91dCAtIHNlY3Rpb24gbWV0cmljcwoKQ1YtPgAYBjoAHw4KbG9vcCBmb3IgZWFjaAAxCAogICAgCiAgICAATQYtPkNWOgBOCEluZm9BdEluZGV4OgAkBUNWLQBUClJldHVybiBQSU1hc29ucnlTACsKCgBFDQAOFDogY29sdW1uQ291bnQAgQAFADQUAFwSACYQAEYed2lkdGhPZkMAZgUAgT0NAEgmADsFAIIRDQCCUwhVc2UAgmsJZW5kCgoAgjgHAII6BihPSykK&s=napkin diff --git a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj index 21f17c4b12..75e17d52f7 100644 --- a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj +++ b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj @@ -21,9 +21,7 @@ F729B8C91D2E176700C9EDBC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F729B8C71D2E176700C9EDBC /* LaunchScreen.storyboard */; }; F729B8D11D2E17A300C9EDBC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 058968F71ABCE06E0059CE2A /* AppDelegate.m */; }; F729B8D21D2E17A300C9EDBC /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 058968FA1ABCE06E0059CE2A /* ViewController.m */; }; - F729B8D31D2E17C800C9EDBC /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7CE6CB31D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */; }; - F729B8D41D2E17C800C9EDBC /* AsyncDisplayKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F7CE6CB31D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - F7CE6CB61D2CE00800BE4C15 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F7CE6CAD1D2CDFFB00BE4C15 /* libAsyncDisplayKit.a */; }; + F7CE6CB61D2CE00800BE4C15 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7CE6CAD1D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -55,18 +53,11 @@ remoteGlobalIDString = 057D02BF1AC0A66700C7AC3C; remoteInfo = AsyncDisplayKitTestHost; }; - F7CE6CB21D2CDFFB00BE4C15 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = B35061DA1B010EDF0018CF92; - remoteInfo = "AsyncDisplayKit-iOS"; - }; F7CE6CB41D2CE00300BE4C15 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; proxyType = 1; - remoteGlobalIDString = 058D09AB195D04C000B7D73C; + remoteGlobalIDString = B35061D91B010EDF0018CF92; remoteInfo = AsyncDisplayKit; }; /* End PBXContainerItemProxy section */ @@ -78,7 +69,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - F729B8D41D2E17C800C9EDBC /* AsyncDisplayKit.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -112,7 +102,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F7CE6CB61D2CE00800BE4C15 /* libAsyncDisplayKit.a in Frameworks */, + F7CE6CB61D2CE00800BE4C15 /* AsyncDisplayKit.framework in Frameworks */, 92DD2FEC1BF4D8BB0074C9DD /* MapKit.framework in Frameworks */, 0589692C1ABCE1820059CE2A /* Photos.framework in Frameworks */, 0589692A1ABCE17C0059CE2A /* AssetsLibrary.framework in Frameworks */, @@ -123,7 +113,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F729B8D31D2E17C800C9EDBC /* AsyncDisplayKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -201,10 +190,9 @@ F7CE6CA61D2CDFFB00BE4C15 /* Products */ = { isa = PBXGroup; children = ( - F7CE6CAD1D2CDFFB00BE4C15 /* libAsyncDisplayKit.a */, + F7CE6CAD1D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */, F7CE6CAF1D2CDFFB00BE4C15 /* AsyncDisplayKitTests.xctest */, F7CE6CB11D2CDFFB00BE4C15 /* AsyncDisplayKitTestHost.app */, - F7CE6CB31D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */, ); name = Products; sourceTree = ""; @@ -292,10 +280,10 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - F7CE6CAD1D2CDFFB00BE4C15 /* libAsyncDisplayKit.a */ = { + F7CE6CAD1D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libAsyncDisplayKit.a; + fileType = wrapper.framework; + path = AsyncDisplayKit.framework; remoteRef = F7CE6CAC1D2CDFFB00BE4C15 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -313,13 +301,6 @@ remoteRef = F7CE6CB01D2CDFFB00BE4C15 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - F7CE6CB31D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = AsyncDisplayKit.framework; - remoteRef = F7CE6CB21D2CDFFB00BE4C15 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ @@ -478,7 +459,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = "Life Without CocoaPods/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -495,7 +475,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = "Life Without CocoaPods/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -568,6 +547,7 @@ F729B8CC1D2E176700C9EDBC /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ };