From 003727746e4b265e8a2d3104624fb3e33fc1c025 Mon Sep 17 00:00:00 2001 From: Engin Kurutepe Date: Tue, 23 Feb 2016 15:00:56 +0100 Subject: [PATCH 01/27] Add an example project which tests Carthage build --- examples/CarthageBuildTest/Cartfile | 1 + examples/CarthageBuildTest/Cartfile.resolved | 1 + .../CarthageExample.xcodeproj/project.pbxproj | 349 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../CarthageExample/AppDelegate.h | 17 + .../CarthageExample/AppDelegate.m | 47 +++ .../AppIcon.appiconset/Contents.json | 38 ++ .../Base.lproj/LaunchScreen.storyboard | 27 ++ .../Base.lproj/Main.storyboard | 25 ++ .../CarthageExample/Info.plist | 40 ++ .../CarthageExample/ViewController.h | 15 + .../CarthageExample/ViewController.m | 36 ++ .../CarthageBuildTest/CarthageExample/main.m | 16 + 13 files changed, 619 insertions(+) create mode 100644 examples/CarthageBuildTest/Cartfile create mode 100644 examples/CarthageBuildTest/Cartfile.resolved create mode 100644 examples/CarthageBuildTest/CarthageExample.xcodeproj/project.pbxproj create mode 100644 examples/CarthageBuildTest/CarthageExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/CarthageBuildTest/CarthageExample/AppDelegate.h create mode 100644 examples/CarthageBuildTest/CarthageExample/AppDelegate.m create mode 100644 examples/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 examples/CarthageBuildTest/CarthageExample/Base.lproj/LaunchScreen.storyboard create mode 100644 examples/CarthageBuildTest/CarthageExample/Base.lproj/Main.storyboard create mode 100644 examples/CarthageBuildTest/CarthageExample/Info.plist create mode 100644 examples/CarthageBuildTest/CarthageExample/ViewController.h create mode 100644 examples/CarthageBuildTest/CarthageExample/ViewController.m create mode 100644 examples/CarthageBuildTest/CarthageExample/main.m diff --git a/examples/CarthageBuildTest/Cartfile b/examples/CarthageBuildTest/Cartfile new file mode 100644 index 00000000..aa14143b --- /dev/null +++ b/examples/CarthageBuildTest/Cartfile @@ -0,0 +1 @@ +github "facebook/AsyncDisplayKit" "master" diff --git a/examples/CarthageBuildTest/Cartfile.resolved b/examples/CarthageBuildTest/Cartfile.resolved new file mode 100644 index 00000000..b30ffeb4 --- /dev/null +++ b/examples/CarthageBuildTest/Cartfile.resolved @@ -0,0 +1 @@ +github "facebook/AsyncDisplayKit" "92f87756b3635f59fdd6c9f6c497209e3059ac35" diff --git a/examples/CarthageBuildTest/CarthageExample.xcodeproj/project.pbxproj b/examples/CarthageBuildTest/CarthageExample.xcodeproj/project.pbxproj new file mode 100644 index 00000000..cffd86f3 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample.xcodeproj/project.pbxproj @@ -0,0 +1,349 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 871BB34E1C7C98B1005CF62A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 871BB34D1C7C98B1005CF62A /* main.m */; }; + 871BB3511C7C98B1005CF62A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 871BB3501C7C98B1005CF62A /* AppDelegate.m */; }; + 871BB3541C7C98B1005CF62A /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 871BB3531C7C98B1005CF62A /* ViewController.m */; }; + 871BB3571C7C98B1005CF62A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 871BB3551C7C98B1005CF62A /* Main.storyboard */; }; + 871BB3591C7C98B1005CF62A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 871BB3581C7C98B1005CF62A /* Assets.xcassets */; }; + 871BB35C1C7C98B1005CF62A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 871BB35A1C7C98B1005CF62A /* LaunchScreen.storyboard */; }; + 871BB3651C7C99B0005CF62A /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 871BB3641C7C99B0005CF62A /* AsyncDisplayKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 871BB3491C7C98B1005CF62A /* CarthageExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CarthageExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 871BB34D1C7C98B1005CF62A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 871BB34F1C7C98B1005CF62A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 871BB3501C7C98B1005CF62A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 871BB3521C7C98B1005CF62A /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 871BB3531C7C98B1005CF62A /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 871BB3561C7C98B1005CF62A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 871BB3581C7C98B1005CF62A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 871BB35B1C7C98B1005CF62A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 871BB35D1C7C98B1005CF62A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 871BB3641C7C99B0005CF62A /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AsyncDisplayKit.framework; path = Carthage/Build/iOS/AsyncDisplayKit.framework; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 871BB3461C7C98B1005CF62A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 871BB3651C7C99B0005CF62A /* AsyncDisplayKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 871BB3401C7C98B1005CF62A = { + isa = PBXGroup; + children = ( + 871BB34B1C7C98B1005CF62A /* CarthageExample */, + 871BB34A1C7C98B1005CF62A /* Products */, + ); + sourceTree = ""; + }; + 871BB34A1C7C98B1005CF62A /* Products */ = { + isa = PBXGroup; + children = ( + 871BB3491C7C98B1005CF62A /* CarthageExample.app */, + ); + name = Products; + sourceTree = ""; + }; + 871BB34B1C7C98B1005CF62A /* CarthageExample */ = { + isa = PBXGroup; + children = ( + 871BB34F1C7C98B1005CF62A /* AppDelegate.h */, + 871BB3501C7C98B1005CF62A /* AppDelegate.m */, + 871BB3581C7C98B1005CF62A /* Assets.xcassets */, + 871BB3631C7C9994005CF62A /* Frameworks */, + 871BB35D1C7C98B1005CF62A /* Info.plist */, + 871BB35A1C7C98B1005CF62A /* LaunchScreen.storyboard */, + 871BB3551C7C98B1005CF62A /* Main.storyboard */, + 871BB34C1C7C98B1005CF62A /* Supporting Files */, + 871BB3521C7C98B1005CF62A /* ViewController.h */, + 871BB3531C7C98B1005CF62A /* ViewController.m */, + ); + path = CarthageExample; + sourceTree = ""; + }; + 871BB34C1C7C98B1005CF62A /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 871BB34D1C7C98B1005CF62A /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 871BB3631C7C9994005CF62A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 871BB3641C7C99B0005CF62A /* AsyncDisplayKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 871BB3481C7C98B1005CF62A /* CarthageExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 871BB3601C7C98B1005CF62A /* Build configuration list for PBXNativeTarget "CarthageExample" */; + buildPhases = ( + 871BB3451C7C98B1005CF62A /* Sources */, + 871BB3461C7C98B1005CF62A /* Frameworks */, + 871BB3471C7C98B1005CF62A /* Resources */, + 871BB3661C7C99B8005CF62A /* Copy Framworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CarthageExample; + productName = CarthageExample; + productReference = 871BB3491C7C98B1005CF62A /* CarthageExample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 871BB3411C7C98B1005CF62A /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = "Engin Kurutepe"; + TargetAttributes = { + 871BB3481C7C98B1005CF62A = { + CreatedOnToolsVersion = 7.2.1; + }; + }; + }; + buildConfigurationList = 871BB3441C7C98B1005CF62A /* Build configuration list for PBXProject "CarthageExample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 871BB3401C7C98B1005CF62A; + productRefGroup = 871BB34A1C7C98B1005CF62A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 871BB3481C7C98B1005CF62A /* CarthageExample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 871BB3471C7C98B1005CF62A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 871BB35C1C7C98B1005CF62A /* LaunchScreen.storyboard in Resources */, + 871BB3591C7C98B1005CF62A /* Assets.xcassets in Resources */, + 871BB3571C7C98B1005CF62A /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 871BB3661C7C99B8005CF62A /* Copy Framworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/Carthage/Build/iOS/AsyncDisplayKit.framework", + ); + name = "Copy Framworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/local/bin/carthage copy-frameworks"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 871BB3451C7C98B1005CF62A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 871BB3541C7C98B1005CF62A /* ViewController.m in Sources */, + 871BB3511C7C98B1005CF62A /* AppDelegate.m in Sources */, + 871BB34E1C7C98B1005CF62A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 871BB3551C7C98B1005CF62A /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 871BB3561C7C98B1005CF62A /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 871BB35A1C7C98B1005CF62A /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 871BB35B1C7C98B1005CF62A /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 871BB35E1C7C98B1005CF62A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 871BB35F1C7C98B1005CF62A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 871BB3611C7C98B1005CF62A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = CarthageExample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.asyncdisplaykit.CarthageExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 871BB3621C7C98B1005CF62A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = CarthageExample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.asyncdisplaykit.CarthageExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 871BB3441C7C98B1005CF62A /* Build configuration list for PBXProject "CarthageExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 871BB35E1C7C98B1005CF62A /* Debug */, + 871BB35F1C7C98B1005CF62A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 871BB3601C7C98B1005CF62A /* Build configuration list for PBXNativeTarget "CarthageExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 871BB3611C7C98B1005CF62A /* Debug */, + 871BB3621C7C98B1005CF62A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 871BB3411C7C98B1005CF62A /* Project object */; +} diff --git a/examples/CarthageBuildTest/CarthageExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/CarthageBuildTest/CarthageExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..ef35de35 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/CarthageBuildTest/CarthageExample/AppDelegate.h b/examples/CarthageBuildTest/CarthageExample/AppDelegate.h new file mode 100644 index 00000000..6f3bc417 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/AppDelegate.h @@ -0,0 +1,17 @@ +// +// AppDelegate.h +// CarthageExample +// +// Created by Engin Kurutepe on 23/02/16. +// Copyright © 2016 Engin Kurutepe. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/examples/CarthageBuildTest/CarthageExample/AppDelegate.m b/examples/CarthageBuildTest/CarthageExample/AppDelegate.m new file mode 100644 index 00000000..225a9f70 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/AppDelegate.m @@ -0,0 +1,47 @@ +// +// AppDelegate.m +// CarthageExample +// +// Created by Engin Kurutepe on 23/02/16. +// Copyright © 2016 Engin Kurutepe. All rights reserved. +// + +@import AsyncDisplayKit; + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/examples/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..118c98f7 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CarthageBuildTest/CarthageExample/Base.lproj/LaunchScreen.storyboard b/examples/CarthageBuildTest/CarthageExample/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..2e721e18 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/CarthageBuildTest/CarthageExample/Base.lproj/Main.storyboard b/examples/CarthageBuildTest/CarthageExample/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f56d2f3b --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/Base.lproj/Main.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/CarthageBuildTest/CarthageExample/Info.plist b/examples/CarthageBuildTest/CarthageExample/Info.plist new file mode 100644 index 00000000..6905cc67 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/CarthageBuildTest/CarthageExample/ViewController.h b/examples/CarthageBuildTest/CarthageExample/ViewController.h new file mode 100644 index 00000000..4cf7ecbc --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/ViewController.h @@ -0,0 +1,15 @@ +// +// ViewController.h +// CarthageExample +// +// Created by Engin Kurutepe on 23/02/16. +// Copyright © 2016 Engin Kurutepe. All rights reserved. +// + +#import + +@interface ViewController : UIViewController + + +@end + diff --git a/examples/CarthageBuildTest/CarthageExample/ViewController.m b/examples/CarthageBuildTest/CarthageExample/ViewController.m new file mode 100644 index 00000000..a65c7f7d --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/ViewController.m @@ -0,0 +1,36 @@ +// +// ViewController.m +// CarthageExample +// +// Created by Engin Kurutepe on 23/02/16. +// Copyright © 2016 Engin Kurutepe. All rights reserved. +// + +@import AsyncDisplayKit; + +#import "ViewController.h" + +@interface ViewController () + +@end + +@implementation ViewController + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + CGSize screenSize = self.view.bounds.size; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + ASTextNode *node = [[ASTextNode alloc] init]; + node.attributedString = [[NSAttributedString alloc] initWithString:@"hello world"]; + [node measure:(CGSize){.width = screenSize.width, .height = CGFLOAT_MAX}]; + node.frame = (CGRect) {.origin = (CGPoint){.x = 100, .y = 100}, .size = node.calculatedSize }; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.view addSubview:node.view]; + }); + }); +} + +@end diff --git a/examples/CarthageBuildTest/CarthageExample/main.m b/examples/CarthageBuildTest/CarthageExample/main.m new file mode 100644 index 00000000..e37b2787 --- /dev/null +++ b/examples/CarthageBuildTest/CarthageExample/main.m @@ -0,0 +1,16 @@ +// +// main.m +// CarthageExample +// +// Created by Engin Kurutepe on 23/02/16. +// Copyright © 2016 Engin Kurutepe. All rights reserved. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} From 1703bd77ee21faa44eb20499003f4fb2665ba482 Mon Sep 17 00:00:00 2001 From: Engin Kurutepe Date: Tue, 23 Feb 2016 15:06:40 +0100 Subject: [PATCH 02/27] add README.md --- examples/CarthageBuildTest/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 examples/CarthageBuildTest/README.md diff --git a/examples/CarthageBuildTest/README.md b/examples/CarthageBuildTest/README.md new file mode 100644 index 00000000..65ad6a67 --- /dev/null +++ b/examples/CarthageBuildTest/README.md @@ -0,0 +1,7 @@ +This project is supposed to test that the `AsyncDisplayKit.framework` built by Carthage from the master branch can be imported as a module without causing any warnings and errors. + +Steps to verify: + +- Run `carthage update --platform iOS` +- Build `CarthageExample.xcodeproj` +- Verify that there are 0 Errors and 0 Warnings From bb856d87862f92120c28b622ab63b4aa28500c4d Mon Sep 17 00:00:00 2001 From: Engin Kurutepe Date: Wed, 24 Feb 2016 15:05:37 +0100 Subject: [PATCH 03/27] update build script and travis settings --- .travis.yml | 1 + build.sh | 32 +++++++++++++++---- examples/CarthageBuildTest/Cartfile.resolved | 2 +- .../project.pbxproj | 0 .../contents.xcworkspacedata | 0 5 files changed, 27 insertions(+), 8 deletions(-) rename examples/CarthageBuildTest/{CarthageExample.xcodeproj => Sample.xcodeproj}/project.pbxproj (100%) rename examples/CarthageBuildTest/{CarthageExample.xcodeproj => Sample.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (100%) diff --git a/.travis.yml b/.travis.yml index fd3259eb..2f5f7707 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ osx_image: xcode7.2 before_install: - brew update - brew reinstall xctool + - brew reinstall carthage - gem install cocoapods -v 0.38.2 - gem install slather - xcrun simctl list diff --git a/build.sh b/build.sh index d2f19504..e916181e 100755 --- a/build.sh +++ b/build.sh @@ -35,13 +35,31 @@ if [ "$MODE" = "examples" ]; then for example in examples/*/; do echo "Building $example." - pod install --project-directory=$example - xctool \ - -workspace "${example}Sample.xcworkspace" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build + + if [ -f "${example}/Podfile" ]; then + echo "Using CocoaPods" + pod install --project-directory=$example + + xctool \ + -workspace "${example}Sample.xcworkspace" \ + -scheme Sample \ + -sdk "$SDK" \ + -destination "$PLATFORM" \ + build + elif [ -f "${example}/Cartfile" ]; then + echo "Using Carthage" + cd $example + carthage update --platform iOS + + xctool \ + -project "Sample.xcodeproj" \ + -scheme Sample \ + -sdk "$SDK" \ + -destination "$PLATFORM" \ + build + + cd ../.. + fi done trap - EXIT exit 0 diff --git a/examples/CarthageBuildTest/Cartfile.resolved b/examples/CarthageBuildTest/Cartfile.resolved index b30ffeb4..52282a91 100644 --- a/examples/CarthageBuildTest/Cartfile.resolved +++ b/examples/CarthageBuildTest/Cartfile.resolved @@ -1 +1 @@ -github "facebook/AsyncDisplayKit" "92f87756b3635f59fdd6c9f6c497209e3059ac35" +github "facebook/AsyncDisplayKit" "eb6aa28d42f3dd547f048d0a4d5cd4787f0647b0" diff --git a/examples/CarthageBuildTest/CarthageExample.xcodeproj/project.pbxproj b/examples/CarthageBuildTest/Sample.xcodeproj/project.pbxproj similarity index 100% rename from examples/CarthageBuildTest/CarthageExample.xcodeproj/project.pbxproj rename to examples/CarthageBuildTest/Sample.xcodeproj/project.pbxproj diff --git a/examples/CarthageBuildTest/CarthageExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/CarthageBuildTest/CarthageExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata From 02238933150c99c5a75c22eeb939b1854ced5023 Mon Sep 17 00:00:00 2001 From: Engin Kurutepe Date: Tue, 15 Mar 2016 18:13:49 +0100 Subject: [PATCH 04/27] generate Cartfile using the local path --- build.sh | 3 +++ examples/CarthageBuildTest/Cartfile | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index e916181e..1f3884c9 100755 --- a/build.sh +++ b/build.sh @@ -48,7 +48,10 @@ if [ "$MODE" = "examples" ]; then build elif [ -f "${example}/Cartfile" ]; then echo "Using Carthage" + local_repo=`pwd` cd $example + + echo "git \"file://${local_repo}\" \"master\"" > "Cartfile" carthage update --platform iOS xctool \ diff --git a/examples/CarthageBuildTest/Cartfile b/examples/CarthageBuildTest/Cartfile index aa14143b..75ef5512 100644 --- a/examples/CarthageBuildTest/Cartfile +++ b/examples/CarthageBuildTest/Cartfile @@ -1 +1 @@ -github "facebook/AsyncDisplayKit" "master" +git "file:///build.sh/will/put/local/absolute/path/here" "master" From 229bce7f422b76b045fef2a5058215447ad310c0 Mon Sep 17 00:00:00 2001 From: Engin Kurutepe Date: Thu, 17 Mar 2016 14:49:14 +0100 Subject: [PATCH 05/27] disable autocreate schemes --- .../xcshareddata/WorkspaceSettings.xcsettings | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..08de0be8 --- /dev/null +++ b/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + From e11ed66d9992860d82c7e127443354f13b8a0752 Mon Sep 17 00:00:00 2001 From: Engin Kurutepe Date: Thu, 17 Mar 2016 15:00:32 +0100 Subject: [PATCH 06/27] use current-branch in carthage build --- .../project.xcworkspace/contents.xcworkspacedata | 2 +- build.sh | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/AsyncDisplayKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 235ea95f..919434a6 100644 --- a/AsyncDisplayKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/AsyncDisplayKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/build.sh b/build.sh index 1f3884c9..0d7fa4cd 100755 --- a/build.sh +++ b/build.sh @@ -49,9 +49,10 @@ if [ "$MODE" = "examples" ]; then 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}\" \"master\"" > "Cartfile" + echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile" carthage update --platform iOS xctool \ From ad09d83f19fabfa063d65e7a3fc6107a573546f3 Mon Sep 17 00:00:00 2001 From: Engin Kurutepe Date: Thu, 17 Mar 2016 15:22:00 +0100 Subject: [PATCH 07/27] make the sample scheme shared --- .../xcshareddata/WorkspaceSettings.xcsettings | 8 ++ .../xcshareddata/xcschemes/Sample.xcscheme | 80 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 examples/CarthageBuildTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme diff --git a/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..08de0be8 --- /dev/null +++ b/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + diff --git a/examples/CarthageBuildTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/CarthageBuildTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 00000000..8b3132f2 --- /dev/null +++ b/examples/CarthageBuildTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 2a978054f214ef78ca06d9eff478d1e1d05933b9 Mon Sep 17 00:00:00 2001 From: Engin Kurutepe Date: Thu, 17 Mar 2016 20:27:03 +0100 Subject: [PATCH 08/27] remove Cartfile.resolved --- examples/CarthageBuildTest/Cartfile.resolved | 1 - 1 file changed, 1 deletion(-) delete mode 100644 examples/CarthageBuildTest/Cartfile.resolved diff --git a/examples/CarthageBuildTest/Cartfile.resolved b/examples/CarthageBuildTest/Cartfile.resolved deleted file mode 100644 index 52282a91..00000000 --- a/examples/CarthageBuildTest/Cartfile.resolved +++ /dev/null @@ -1 +0,0 @@ -github "facebook/AsyncDisplayKit" "eb6aa28d42f3dd547f048d0a4d5cd4787f0647b0" From c5c7abb1d65992eda448e4f4e34b26265bf587e2 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 18 Mar 2016 21:13:26 -0700 Subject: [PATCH 09/27] [ASDisplayNode] Placeholders should always be recreated if returning to past nodes. If previously-displayed contents is gone (e.g. clearContents), and is not finished displaying by the time the node is onscreen, recreate the placeholder immediately. --- AsyncDisplayKit/ASDisplayNode+Beta.h | 7 ++ AsyncDisplayKit/ASDisplayNode.mm | 112 ++++++++++++------------ AsyncDisplayKit/ASImageNode.mm | 88 ++++++++++--------- AsyncDisplayKit/ASMultiplexImageNode.mm | 5 ++ AsyncDisplayKit/ASNetworkImageNode.mm | 7 ++ 5 files changed, 126 insertions(+), 93 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index 1ea3d337..11cd6d0e 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -91,4 +91,11 @@ ASDISPLAYNODE_EXTERN_C_END shouldMeasureAsync:(BOOL)shouldMeasureAsync measurementCompletion:(void(^)())completion; + +/** + * @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network. + * Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished. + */ +- (BOOL)placeholderShouldPersist; + @end diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 89fb8d2b..7ea1bc6c 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -420,6 +420,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { CALayer *layer; ASDN::MutexLocker l(_propertyLock); + ASDisplayNodeAssert(_flags.layerBacked, @"_layerToLoad is only for layer-backed nodes"); if (_layerBlock) { layer = _layerBlock(); @@ -475,10 +476,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) TIME_SCOPED(_debugTimeForDidLoad); [self __didLoad]; } - - if (self.placeholderEnabled) { - [self _setupPlaceholderLayer]; - } } - (UIView *)view @@ -743,17 +740,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDN::MutexLocker l(_propertyLock); [self calculatedLayoutDidChange]; - // we generate placeholders at measureWithSizeRange: time so that a node is guaranteed - // to have a placeholder ready to go. Also, if a node has no size it should not have a placeholder - if (self.placeholderEnabled && [self _displaysAsynchronously] && - _layout.size.width > 0.0 && _layout.size.height > 0.0) { + // We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go. + // This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously. + // First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync. + if (_placeholderEnabled && [self _displaysAsynchronously] && self.contents == nil) { + + // Zero-sized nodes do not require a placeholder. + CGSize layoutSize = (_layout ? _layout.size : CGSizeZero); + if (CGSizeEqualToSize(layoutSize, CGSizeZero)) { + return; + } + if (!_placeholderImage) { _placeholderImage = [self placeholderImage]; } - - if (_placeholderLayer) { - [self _setupPlaceholderLayerContents]; - } } } @@ -1004,13 +1004,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(_propertyLock); - if (CGRectEqualToRect(self.bounds, CGRectZero)) { + CGRect bounds = self.bounds; + if (CGRectEqualToRect(bounds, CGRectZero)) { // Performing layout on a zero-bounds view often results in frame calculations // with negative sizes after applying margins, which will cause // measureWithSizeRange: on subnodes to assert. return; } - _placeholderLayer.frame = self.bounds; + _placeholderLayer.frame = bounds; [self layout]; [self layoutDidFinish]; } @@ -1539,9 +1540,20 @@ static NSInteger incrementIfFound(NSInteger i) { } _flags.isEnteringHierarchy = NO; - CALayer *layer = self.layer; - if (!layer.contents) { + + // If we don't have contents finished drawing by the time we are on screen, immediately add the placeholder (if it is enabled and we do have something to draw). + if (self.contents == nil) { + CALayer *layer = self.layer; [layer setNeedsDisplay]; + + if ([self _shouldHavePlaceholderLayer]) { + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + [self _setupPlaceholderLayerIfNeeded]; + _placeholderLayer.opacity = 1.0; + [CATransaction commit]; + [self.layer addSublayer:_placeholderLayer]; + } } } } @@ -1671,10 +1683,9 @@ static NSInteger incrementIfFound(NSInteger i) { [_pendingDisplayNodes removeObject:node]; - // only trampoline if there is a placeholder and nodes are done displaying - if ([self _pendingDisplayNodesHaveFinished] && _placeholderLayer.superlayer) { + if (_pendingDisplayNodes.count == 0 && _placeholderLayer.superlayer && ![self placeholderShouldPersist]) { void (^cleanupBlock)() = ^{ - [self _tearDownPlaceholderLayer]; + [_placeholderLayer removeFromSuperlayer]; }; if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) { @@ -1689,33 +1700,46 @@ static NSInteger incrementIfFound(NSInteger i) { } } -// Helper method to check that all nodes that the current node is waiting to display are finished -// Use this method to check to remove any placeholder layers -- (BOOL)_pendingDisplayNodesHaveFinished -{ - return _pendingDisplayNodes.count == 0; -} - // Helper method to summarize whether or not the node run through the display process - (BOOL)__implementsDisplay { return _flags.implementsDrawRect || _flags.implementsImageDisplay || self.shouldRasterizeDescendants || _flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay; } -- (void)_setupPlaceholderLayer +- (BOOL)placeholderShouldPersist { - ASDisplayNodeAssertMainThread(); - - _placeholderLayer = [CALayer layer]; - // do not set to CGFLOAT_MAX in the case that something needs to be overtop the placeholder - _placeholderLayer.zPosition = 9999.0; + return NO; } -- (void)_tearDownPlaceholderLayer +- (BOOL)_shouldHavePlaceholderLayer +{ + return (_placeholderEnabled && [self __implementsDisplay]); +} + +- (void)_setupPlaceholderLayerIfNeeded { ASDisplayNodeAssertMainThread(); - [_placeholderLayer removeFromSuperlayer]; + 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; + } + + if (_placeholderLayer.contents == nil) { + if (!_placeholderImage) { + _placeholderImage = [self placeholderImage]; + } + if (_placeholderImage) { + BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(_placeholderImage.capInsets, UIEdgeInsetsZero); + if (stretchable) { + ASDisplayNodeSetupLayerContentsWithResizableImage(_placeholderLayer, _placeholderImage); + } else { + _placeholderLayer.contentsScale = self.contentsScale; + _placeholderLayer.contents = (id)_placeholderImage.CGImage; + } + } + } } void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) @@ -2252,26 +2276,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [self _pendingNodeWillDisplay:self]; [_supernode subnodeDisplayWillStart:self]; - - if (_placeholderImage && _placeholderLayer && self.layer.contents == nil) { - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - [self _setupPlaceholderLayerContents]; - _placeholderLayer.opacity = 1.0; - [CATransaction commit]; - [self.layer addSublayer:_placeholderLayer]; - } -} - -- (void)_setupPlaceholderLayerContents -{ - BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(_placeholderImage.capInsets, UIEdgeInsetsZero); - if (stretchable) { - ASDisplayNodeSetupLayerContentsWithResizableImage(_placeholderLayer, _placeholderImage); - } else { - _placeholderLayer.contentsScale = self.contentsScale; - _placeholderLayer.contents = (id)_placeholderImage.CGImage; - } } - (void)displayDidFinish diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 66fa1fdf..7c27ed56 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -25,6 +25,7 @@ @interface _ASImageNodeDrawParameters : NSObject +@property (nonatomic, retain) UIImage *image; @property (nonatomic, assign) BOOL opaque; @property (nonatomic, assign) CGRect bounds; @property (nonatomic, assign) CGFloat contentsScale; @@ -36,11 +37,17 @@ // TODO: eliminate explicit parameters with a set of keys copied from the node @implementation _ASImageNodeDrawParameters -- (id)initWithBounds:(CGRect)bounds opaque:(BOOL)opaque contentsScale:(CGFloat)contentsScale backgroundColor:(UIColor *)backgroundColor contentMode:(UIViewContentMode)contentMode +- (id)initWithImage:(UIImage *)image + bounds:(CGRect)bounds + opaque:(BOOL)opaque + contentsScale:(CGFloat)contentsScale + backgroundColor:(UIColor *)backgroundColor + contentMode:(UIViewContentMode)contentMode { - self = [self init]; - if (!self) return nil; + if (!(self = [self init])) + return nil; + _image = image; _opaque = opaque; _bounds = bounds; _contentsScale = contentsScale; @@ -133,8 +140,13 @@ _image = image; _imageLock.unlock(); + [self invalidateCalculatedLayout]; - [self setNeedsDisplay]; + if (image) { + [self setNeedsDisplay]; + } else { + self.contents = nil; + } } else { _imageLock.unlock(); // We avoid using MutexUnlocker as it needlessly re-locks at the end of the scope. } @@ -156,11 +168,12 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { - return [[_ASImageNodeDrawParameters alloc] initWithBounds:self.bounds - opaque:self.opaque - contentsScale:self.contentsScaleForDisplay - backgroundColor:self.backgroundColor - contentMode:self.contentMode]; + return [[_ASImageNodeDrawParameters alloc] initWithImage:self.image + bounds:self.bounds + opaque:self.opaque + contentsScale:self.contentsScaleForDisplay + backgroundColor:self.backgroundColor + contentMode:self.contentMode]; } - (NSDictionary *)debugLabelAttributes @@ -171,21 +184,26 @@ - (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled { - UIImage *image; - BOOL cropEnabled; - BOOL forceUpscaling; - CGFloat contentsScale; - CGRect cropDisplayBounds; - CGRect cropRect; + UIImage *image = parameters.image; + if (!image) { + return nil; + } + + BOOL forceUpscaling = NO; + BOOL cropEnabled = NO; + BOOL isOpaque = parameters.opaque; + UIColor *backgroundColor = parameters.backgroundColor; + UIViewContentMode contentMode = parameters.contentMode; + CGFloat contentsScale = 0.0; + CGRect cropDisplayBounds = CGRectZero; + CGRect cropRect = CGRectZero; asimagenode_modification_block_t imageModificationBlock; { ASDN::MutexLocker l(_imageLock); - image = _image; - if (!image) { - return nil; - } + // FIXME: There is a small risk of these values changing between the main thread creation of drawParameters, and the execution of this method. + // We should package these up into the draw parameters object. Might be easiest to create a struct for the non-objects and make it one property. cropEnabled = _cropEnabled; forceUpscaling = _forceUpscaling; contentsScale = _contentsScaleForDisplay; @@ -194,16 +212,12 @@ imageModificationBlock = _imageModificationBlock; } + BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds); + CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : parameters.bounds); + ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentWithRenderingContext; ASDisplayNodeContextModifier postContextBlock = self.didDisplayNodeContentWithRenderingContext; - BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds); - - CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : parameters.bounds); - BOOL isOpaque = parameters.opaque; - UIColor *backgroundColor = parameters.backgroundColor; - UIViewContentMode contentMode = parameters.contentMode; - ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time"); // if the image is resizable, bail early since the image has likely already been configured @@ -232,17 +246,15 @@ } } - BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill - || contentMode == UIViewContentModeScaleAspectFit - || contentMode == UIViewContentModeCenter; + BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill || + contentMode == UIViewContentModeScaleAspectFit || + contentMode == UIViewContentModeCenter; - CGSize backingSize; - CGRect imageDrawRect; + CGSize backingSize = CGSizeZero; + CGRect imageDrawRect = CGRectZero; - if (boundsSizeInPixels.width * contentsScale < 1.0f || - boundsSizeInPixels.height * contentsScale < 1.0f || - imageSizeInPixels.width < 1.0f || - imageSizeInPixels.height < 1.0f) { + if (boundsSizeInPixels.width * contentsScale < 1.0f || boundsSizeInPixels.height * contentsScale < 1.0f || + imageSizeInPixels.width < 1.0f || imageSizeInPixels.height < 1.0f) { return nil; } @@ -260,10 +272,8 @@ &imageDrawRect); } - if (backingSize.width <= 0.0f || - backingSize.height <= 0.0f || - imageDrawRect.size.width <= 0.0f || - imageDrawRect.size.height <= 0.0f) { + if (backingSize.width <= 0.0f || backingSize.height <= 0.0f || + imageDrawRect.size.width <= 0.0f || imageDrawRect.size.height <= 0.0f) { return nil; } diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index 765deec5..30f17dc4 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -265,6 +265,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent } } +- (BOOL)placeholderShouldPersist +{ + return (self.image == nil && self.imageIdentifiers.count > 0); +} + /* displayWillStart in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary in ASNetworkImageNode as well. */ - (void)displayWillStart diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 1a1ce268..f909de9f 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -173,6 +173,12 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; return _delegate; } +- (BOOL)placeholderShouldPersist +{ + ASDN::MutexLocker l(_lock); + return (self.image == nil && _URL != nil); +} + /* displayWillStart in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary in ASMultiplexImageNode as well. */ - (void)displayWillStart @@ -334,6 +340,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - (void)_lazilyLoadImageIfNecessary { + // FIXME: We should revisit locking in this method (e.g. to access the instance variables at the top, and holding lock while calling delegate) if (!_imageLoaded && _URL != nil && _downloadIdentifier == nil) { { ASDN::MutexLocker l(_lock); From 7bb48d266ccd45f162512bb14d293dd5f78d1c58 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 18 Mar 2016 23:30:26 -0700 Subject: [PATCH 10/27] [ASMapNode] Reduce number of MapKit calls to -cancel by using the .isLoading property. --- AsyncDisplayKit/ASMapNode.mm | 40 +++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index b714255e..ae3d6801 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -134,12 +134,14 @@ - (void)setOptions:(MKMapSnapshotOptions *)options { ASDN::MutexLocker l(_propertyLock); - _options = options; - if (self.isLiveMap) { - [self applySnapshotOptions]; - } else { - [self resetSnapshotter]; - [self takeSnapshot]; + if (!_options || ![options isEqual:_options]) { + _options = options; + if (self.isLiveMap) { + [self applySnapshotOptions]; + } else if (_snapshotter) { + [self destroySnapshotter]; + [self takeSnapshot]; + } } } @@ -160,7 +162,11 @@ if (!_snapshotter) { [self setUpSnapshotter]; } - [_snapshotter cancel]; + + if (_snapshotter.isLoading) { + return; + } + [_snapshotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) completionHandler:^(MKMapSnapshot *snapshot, NSError *error) { if (!error) { @@ -207,12 +213,10 @@ _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; } -- (void)resetSnapshotter +- (void)destroySnapshotter { - // FIXME: The semantics of this method / name would suggest that we cancel + destroy the snapshotter, - // but not that we create a new one. We should probably only create the new one in -takeSnapshot or something. [_snapshotter cancel]; - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; + _snapshotter = nil; } - (void)applySnapshotOptions @@ -273,11 +277,14 @@ } #pragma mark - Layout -- (void)setSnapshotSizeIfNeeded:(CGSize)snapshotSize +- (void)setSnapshotSizeWithReloadIfNeeded:(CGSize)snapshotSize { if (!CGSizeEqualToSize(self.options.size, snapshotSize)) { _options.size = snapshotSize; - [self resetSnapshotter]; + if (_snapshotter) { + [self destroySnapshotter]; + [self takeSnapshot]; + } } } @@ -287,11 +294,11 @@ if (CGSizeEqualToSize(size, CGSizeZero)) { size = constrainedSize; } - [self setSnapshotSizeIfNeeded:size]; + [self setSnapshotSizeWithReloadIfNeeded:size]; return constrainedSize; } -// Layout isn't usually needed in the box model, but since we are making use of MKMapView this is preferred. +// -layout isn't usually needed over -layoutSpecThatFits, but this way we can avoid a needless node wrapper for MKMapView. - (void)layout { [super layout]; @@ -300,10 +307,9 @@ } else { // If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter. if (_needsMapReloadOnBoundsChange) { - [self setSnapshotSizeIfNeeded:self.bounds.size]; + [self setSnapshotSizeWithReloadIfNeeded:self.bounds.size]; // FIXME: Adding a check for FetchData here seems to cause intermittent map load failures, but shouldn't. // if (ASInterfaceStateIncludesFetchData(self.interfaceState)) { - [self takeSnapshot]; } } } From b98920c19f967538dbe272c434696c877fa8972c Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Sat, 19 Mar 2016 17:10:47 -0700 Subject: [PATCH 11/27] [ASVideoNode] Add to ASDK umbrella header --- AsyncDisplayKit/AsyncDisplayKit.h | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 8fbeec47..2bad5cdf 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -14,6 +14,7 @@ #import #import #import +#import #import From f9a92366d3310742e8a05bca600761709031b3c2 Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Sat, 19 Mar 2016 17:54:02 -0700 Subject: [PATCH 12/27] [AsyncDisplayKit] Fix build so we can weak link AVFoundation Unclear why the framework target required these seemingly unrelated changes to Collection classes, but I modeled them after related files and it seems cleaner now (passes tests & framework target) --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 +++++--- AsyncDisplayKit/ASCollectionView.h | 3 +-- AsyncDisplayKit/ASCollectionView.mm | 1 - AsyncDisplayKit/ASVideoNode.h | 11 +++++++---- .../Details/ASCollectionViewFlowLayoutInspector.h | 6 ++++++ AsyncDisplayKitTests/ASVideoNodeTests.m | 3 ++- 6 files changed, 21 insertions(+), 11 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 91db74aa..8fe736fc 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -261,6 +261,7 @@ 69F10C861C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 764D83D51C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */; settings = {ATTRIBUTES = (Public, ); }; }; 764D83D61C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */; }; 767E7F8D1C9019130066C000 /* AsyncDisplayKit+Debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -364,13 +365,13 @@ ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */; }; AEB7B01A1C5962EA00662EF4 /* ASDefaultPlayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */; }; AEB7B01B1C5962EA00662EF4 /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; }; - AEEC47E11C20C2DD00EC1693 /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; }; + AEEC47E11C20C2DD00EC1693 /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; AEEC47E21C20C2DD00EC1693 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.m */; }; B0F8805A1BEAEC7500D17647 /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; B0F8805B1BEAEC7500D17647 /* ASTableNode.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F880591BEAEC7500D17647 /* ASTableNode.m */; }; - B13CA0F71C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; }; - B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; }; + B13CA0F71C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; B13CA1001C52004900E031AB /* ASCollectionNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; }; B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; }; B30BF6521C5964B0004FCD53 /* ASLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; }; @@ -1509,6 +1510,7 @@ B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */, 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */, B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */, + 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */, B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */, B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index b3db9246..99a4bd05 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -13,13 +13,12 @@ #import #import #import +#import @class ASCellNode; @class ASCollectionNode; @protocol ASCollectionDataSource; @protocol ASCollectionDelegate; -@protocol ASCollectionViewLayoutInspecting; -@protocol ASCollectionViewLayoutFacilitatorProtocol; NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 07e01cb1..ae1f5792 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -14,7 +14,6 @@ #import "ASCollectionDataController.h" #import "ASCollectionViewLayoutController.h" #import "ASCollectionViewFlowLayoutInspector.h" -#import "ASCollectionViewLayoutFacilitatorProtocol.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Beta.h" diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index ebc9957c..99768eb8 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -6,13 +6,16 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#import +@class AVAsset, AVPlayer, AVPlayerItem; @protocol ASVideoNodeDelegate; -// This is a relatively new component of AsyncDisplayKit. It has many useful features, but -// there is room for further expansion and optimization. Please report any issues or requests -// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues +// IMPORTANT NOTES: +// 1. Applications using ASVideoNode must link AVFoundation! (this provides the AV* classes below) +// 2. This is a relatively new component of AsyncDisplayKit. It has many useful features, but +// there is room for further expansion and optimization. Please report any issues or requests +// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues @interface ASVideoNode : ASControlNode @property (atomic, strong, readwrite) AVAsset *asset; diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index b2ee2ae8..e8d758cd 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -10,6 +10,9 @@ #import +#ifndef ASCollectionViewLayoutInspecting_h +#define ASCollectionViewLayoutInspecting_h + @class ASCollectionView; @protocol ASCollectionDelegate; @@ -53,3 +56,6 @@ - (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout; @end + +#endif /* ASCollectionViewLayoutInspecting_h */ + diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 43383237..1a4d3f81 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -8,7 +8,8 @@ #import #import -#import "ASVideoNode.h" +#import +#import @interface ASVideoNodeTests : XCTestCase { From fcccb1bf039b45e98c248aad6efedc7ae34e87e8 Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Sat, 19 Mar 2016 18:00:24 -0700 Subject: [PATCH 13/27] [CustomCollectionView Example] Issue (#1366): CustomColelctionView example app always returns YES from shouldInvalidateLayoutForBoundsChange: which triggers a re-layout. Fix: Comparing CGSize rather than CGRect is enough to handle rotation support without always triggering a re-layout. . --- .../CustomCollectionView/Sample/MosaicCollectionViewLayout.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m index 0e2c65d0..67654b0d 100644 --- a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m +++ b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m @@ -119,7 +119,7 @@ - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { - if (!CGRectEqualToRect(self.collectionView.bounds, newBounds)) { + if (!CGSizeEqualToSize(self.collectionView.bounds.size, newBounds.size)) { return YES; } return NO; From d99a13f1a71aca9df2d38c2a591b4946e00ef87c Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Sat, 19 Mar 2016 18:10:08 -0700 Subject: [PATCH 14/27] Revert "[CustomCollectionView Example]" This reverts commit fcccb1bf039b45e98c248aad6efedc7ae34e87e8. --- .../CustomCollectionView/Sample/MosaicCollectionViewLayout.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m index 67654b0d..0e2c65d0 100644 --- a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m +++ b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m @@ -119,7 +119,7 @@ - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { - if (!CGSizeEqualToSize(self.collectionView.bounds.size, newBounds.size)) { + if (!CGRectEqualToRect(self.collectionView.bounds, newBounds)) { return YES; } return NO; From 2dae2d1c09933dc536ecdb9571c0d8d67c4bf6d3 Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Sat, 19 Mar 2016 18:00:24 -0700 Subject: [PATCH 15/27] [CustomCollectionView Example] Issue (#1366): CustomColelctionView example app always returns YES from shouldInvalidateLayoutForBoundsChange: which triggers a re-layout. Fix: Comparing CGSize rather than CGRect is enough to handle rotation support without always triggering a re-layout. . --- .../CustomCollectionView/Sample/MosaicCollectionViewLayout.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m index 0e2c65d0..67654b0d 100644 --- a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m +++ b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m @@ -119,7 +119,7 @@ - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { - if (!CGRectEqualToRect(self.collectionView.bounds, newBounds)) { + if (!CGSizeEqualToSize(self.collectionView.bounds.size, newBounds.size)) { return YES; } return NO; From 8308a4ce49c21b9a5c7fc5168c67ac828cebfc10 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 19 Mar 2016 19:56:41 -0700 Subject: [PATCH 16/27] [AsyncDisplayKit] Adopt #pragma once, remove last remaining dispatch_sync (UIScreen scale). --- ...SCollectionViewLayoutFacilitatorProtocol.h | 5 +---- AsyncDisplayKit/ASDisplayNode.mm | 6 +++++ AsyncDisplayKit/ASMultiplexImageNode.mm | 4 ++-- .../ASCollectionViewFlowLayoutInspector.h | 9 ++------ .../Details/ASDataController+Subclasses.h | 5 +---- AsyncDisplayKit/Details/ASDataController.h | 5 +---- AsyncDisplayKit/Details/ASMainSerialQueue.mm | 9 ++------ AsyncDisplayKit/Private/ASInternalHelpers.mm | 22 +++++-------------- AsyncDisplayKit/TextKit/ASTextKitAttributes.h | 7 ++---- AsyncDisplayKitTests/ASTableViewTests.m | 12 ---------- Base/ASAssert.h | 9 ++++---- 11 files changed, 27 insertions(+), 66 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h b/AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h index b681ceaa..38f43b71 100644 --- a/AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h +++ b/AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h @@ -6,8 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#ifndef ASCollectionViewLayoutFacilitatorProtocol_h -#define ASCollectionViewLayoutFacilitatorProtocol_h +#pragma once /** * This facilitator protocol is intended to help Layout to better @@ -41,5 +40,3 @@ - (void)collectionViewWillPerformBatchUpdates; @end - -#endif /* ASCollectionViewLayoutFacilitatorProtocol_h */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 89fb8d2b..6b42181a 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -198,6 +198,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@"); } ++ (void)load +{ + // Ensure this value is cached on the main thread before needed in the background. + ASScreenScale(); +} + + (BOOL)layerBackedNodesEnabled { return YES; diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index 765deec5..cd7c448e 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -250,7 +250,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent // Delegateify if (_delegateFlags.displayFinish) { - if ([NSThread isMainThread]) + if (ASDisplayNodeThreadIsMain()) [_delegate multiplexImageNodeDidFinishDisplay:self]; else { __weak __typeof__(self) weakSelf = self; @@ -405,7 +405,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent // Delegateify. // Note that we're using the params here instead of self.image and _displayedImageIdentifier because those can change before the async block below executes. if (_delegateFlags.updatedImageDisplayFinish) { - if ([NSThread isMainThread]) + if (ASDisplayNodeThreadIsMain()) [_delegate multiplexImageNode:self didDisplayUpdatedImage:image withIdentifier:displayedImageIdentifier]; else { __weak __typeof__(self) weakSelf = self; diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index e8d758cd..cd640c3e 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -6,13 +6,11 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#pragma once + #import - #import -#ifndef ASCollectionViewLayoutInspecting_h -#define ASCollectionViewLayoutInspecting_h - @class ASCollectionView; @protocol ASCollectionDelegate; @@ -56,6 +54,3 @@ - (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout; @end - -#endif /* ASCollectionViewLayoutInspecting_h */ - diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index ee9a51d2..cb67a203 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -6,8 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#ifndef ASDataControllerSubclasses_Included -#define ASDataControllerSubclasses_Included +#pragma once @class ASIndexedNodeContext; @@ -162,5 +161,3 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NS - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection; @end - -#endif diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 587adf2a..190119ed 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -6,8 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#ifndef ASDataController_Included -#define ASDataController_Included +#pragma once #import #import @@ -195,5 +194,3 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; @end NS_ASSUME_NONNULL_END - -#endif diff --git a/AsyncDisplayKit/Details/ASMainSerialQueue.mm b/AsyncDisplayKit/Details/ASMainSerialQueue.mm index d46a02a1..3112757a 100644 --- a/AsyncDisplayKit/Details/ASMainSerialQueue.mm +++ b/AsyncDisplayKit/Details/ASMainSerialQueue.mm @@ -9,6 +9,7 @@ #import "ASMainSerialQueue.h" #import "ASThread.h" +#import "ASInternalHelpers.h" @interface ASMainSerialQueue () { @@ -55,13 +56,7 @@ } while (true); }; - if ([NSThread isMainThread]) { - mainThread(); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - mainThread(); - }); - } + ASPerformBlockOnMainThread(mainThread); } - (NSString *)description diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.mm b/AsyncDisplayKit/Private/ASInternalHelpers.mm index 0f6aa6f0..40f64454 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.mm +++ b/AsyncDisplayKit/Private/ASInternalHelpers.mm @@ -34,19 +34,6 @@ BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL sele return (superclassIMP != subclassIMP); } -static void ASDispatchOnceOnMainThread(dispatch_once_t *predicate, dispatch_block_t block) -{ - if (ASDisplayNodeThreadIsMain()) { - dispatch_once(predicate, block); - } else { - if (DISPATCH_EXPECT(*predicate == 0L, NO)) { - dispatch_sync(dispatch_get_main_queue(), ^{ - dispatch_once(predicate, block); - }); - } - } -} - void ASPerformBlockOnMainThread(void (^block)()) { if (ASDisplayNodeThreadIsMain()) { @@ -67,12 +54,13 @@ void ASPerformBlockOnBackgroundThread(void (^block)()) CGFloat ASScreenScale() { - static CGFloat _scale; + static CGFloat __scale = 0.0; static dispatch_once_t onceToken; - ASDispatchOnceOnMainThread(&onceToken, ^{ - _scale = [UIScreen mainScreen].scale; + dispatch_once(&onceToken, ^{ + ASDisplayNodeCAssertMainThread(); + __scale = [[UIScreen mainScreen] scale]; }); - return _scale; + return __scale; } CGFloat ASFloorPixelValue(CGFloat f) diff --git a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h index 7fcb8ff1..49dc9b92 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h +++ b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h @@ -8,12 +8,11 @@ * */ +#pragma once + #import #import "ASEqualityHelpers.h" -#ifndef ComponentKit_ASTextKitAttributes_h -#define ComponentKit_ASTextKitAttributes_h - @protocol ASTextKitTruncating; extern NSString *const ASTextKitTruncationAttributeName; @@ -141,5 +140,3 @@ struct ASTextKitAttributes { size_t hash() const; }; - -#endif diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index 550f3fec..2949a16e 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -144,18 +144,6 @@ @implementation ASTableViewTests -- (void)setUp -{ - /// Load a display node before the first test. - /// Without this, running this suite specifically - /// (as opposed to all tests) will cause a deadlock - /// because of the dispatch_sync in `ASScreenScale()`. - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [ASDisplayNode new]; - }); -} - // TODO: Convert this to ARC. - (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate { diff --git a/Base/ASAssert.h b/Base/ASAssert.h index b0b53cc8..446f64b4 100644 --- a/Base/ASAssert.h +++ b/Base/ASAssert.h @@ -9,6 +9,7 @@ #pragma once #import +#import #define ASDisplayNodeAssertWithSignalAndLogFunction(condition, description, logFunction, ...) NSAssert(condition, description, ##__VA_ARGS__); #define ASDisplayNodeCAssertWithSignalAndLogFunction(condition, description, logFunction, ...) NSCAssert(condition, description, ##__VA_ARGS__); @@ -30,11 +31,11 @@ #define ASDisplayNodeAssertNotInstantiable() ASDisplayNodeAssertWithSignal(NO, nil, @"This class is not instantiable."); #define ASDisplayNodeAssertNotSupported() ASDisplayNodeAssertWithSignal(NO, nil, @"This method is not supported by class %@", [self class]); -#define ASDisplayNodeAssertMainThread() ASDisplayNodeAssertWithSignal([NSThread isMainThread], nil, @"This method must be called on the main thread") -#define ASDisplayNodeCAssertMainThread() ASDisplayNodeCAssertWithSignal([NSThread isMainThread], nil, @"This function must be called on the main thread") +#define ASDisplayNodeAssertMainThread() ASDisplayNodeAssertWithSignal(0 != pthread_main_np(), nil, @"This method must be called on the main thread") +#define ASDisplayNodeCAssertMainThread() ASDisplayNodeCAssertWithSignal(0 != pthread_main_np(), nil, @"This function must be called on the main thread") -#define ASDisplayNodeAssertNotMainThread() ASDisplayNodeAssertWithSignal(![NSThread isMainThread], nil, @"This method must be called off the main thread") -#define ASDisplayNodeCAssertNotMainThread() ASDisplayNodeCAssertWithSignal(![NSThread isMainThread], nil, @"This function must be called off the main thread") +#define ASDisplayNodeAssertNotMainThread() ASDisplayNodeAssertWithSignal(0 == pthread_main_np(), nil, @"This method must be called off the main thread") +#define ASDisplayNodeCAssertNotMainThread() ASDisplayNodeCAssertWithSignal(0 == pthread_main_np(), nil, @"This function must be called off the main thread") #define ASDisplayNodeAssertFlag(X) ASDisplayNodeAssertWithSignal((1 == __builtin_popcount(X)), nil, nil) #define ASDisplayNodeCAssertFlag(X) ASDisplayNodeCAssertWithSignal((1 == __builtin_popcount(X)), nil, nil) From 086bd8a52ba10d0ad59bd9ebdc4f11da2a2ee1f3 Mon Sep 17 00:00:00 2001 From: Sam Stow Date: Sat, 23 Jan 2016 15:04:06 -0800 Subject: [PATCH 17/27] [ASRelativeLayoutSpec] New core layout spec type: Relative Position, now powers Center as well. This spec allows positioning a child at any 9-part box position (corners, edges, or center). --- AsyncDisplayKit.xcodeproj/project.pbxproj | 17 +++ AsyncDisplayKit/AsyncDisplayKit.h | 1 + AsyncDisplayKit/Layout/ASCenterLayoutSpec.h | 17 ++- AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm | 68 +++------ AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h | 73 ++++++++++ .../Layout/ASRelativeLayoutSpec.mm | 117 ++++++++++++++++ .../ASCenterLayoutSpecSnapshotTests.mm | 30 ++-- .../ASRelativeLayoutSpecSnapshotTests.mm | 130 ++++++++++++++++++ ...ngeIsGivenToChildWhenNotPositioning@2x.png | Bin 0 -> 4955 bytes .../testWithOptions@2x.png | Bin 0 -> 6221 bytes .../testWithOptions_CenterX@2x.png | Bin 0 -> 6922 bytes .../testWithOptions_CenterXCenterY@2x.png | Bin 0 -> 6662 bytes .../testWithOptions_CenterXEndY@2x.png | Bin 0 -> 6474 bytes .../testWithOptions_CenterY@2x.png | Bin 0 -> 6055 bytes .../testWithOptions_EndX@2x.png | Bin 0 -> 5710 bytes .../testWithOptions_EndXCenterY@2x.png | Bin 0 -> 5811 bytes .../testWithOptions_EndXEndY@2x.png | Bin 0 -> 5534 bytes .../testWithOptions_EndY@2x.png | Bin 0 -> 5894 bytes .../testWithSizingOptions@2x.png | Bin 0 -> 6221 bytes ...thSizingOptions_SizingMinimumHeight@2x.png | Bin 0 -> 4847 bytes ...ithSizingOptions_SizingMinimumWidth@2x.png | Bin 0 -> 2099 bytes ...zingMinimumWidthSizingMinimumHeight@2x.png | Bin 0 -> 1448 bytes ...ngeIsGivenToChildWhenNotPositioning@3x.png | Bin 0 -> 10308 bytes .../testWithOptions@3x.png | Bin 0 -> 12389 bytes .../testWithOptions_CenterX@3x.png | Bin 0 -> 12505 bytes .../testWithOptions_CenterXCenterY@3x.png | Bin 0 -> 12274 bytes .../testWithOptions_CenterXEndY@3x.png | Bin 0 -> 12004 bytes .../testWithOptions_CenterY@3x.png | Bin 0 -> 12114 bytes .../testWithOptions_EndX@3x.png | Bin 0 -> 11609 bytes .../testWithOptions_EndXCenterY@3x.png | Bin 0 -> 11738 bytes .../testWithOptions_EndXEndY@3x.png | Bin 0 -> 11338 bytes .../testWithOptions_EndY@3x.png | Bin 0 -> 11827 bytes .../testWithSizingOptions@3x.png | Bin 0 -> 12389 bytes ...thSizingOptions_SizingMinimumHeight@3x.png | Bin 0 -> 9534 bytes ...ithSizingOptions_SizingMinimumWidth@3x.png | Bin 0 -> 3946 bytes ...zingMinimumWidthSizingMinimumHeight@3x.png | Bin 0 -> 2696 bytes 36 files changed, 380 insertions(+), 73 deletions(-) create mode 100644 AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h create mode 100644 AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm create mode 100644 AsyncDisplayKitTests/ASRelativeLayoutSpecSnapshotTests.mm create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXEndY@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@3x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@3x.png diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 8fe736fc..5ca1e6d8 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -268,6 +268,11 @@ 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */; }; 81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; + 7A06A73A1C35F08800FE8DAA /* ASRelativeLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */; }; + 7A06A73B1C35F08800FE8DAA /* ASRelativeLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */; }; + 7AB338671C55B3460055FDE8 /* ASRelativeLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */; }; 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; @@ -739,6 +744,9 @@ 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AsyncDisplayKit+Debug.m"; sourceTree = ""; }; 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = ""; }; 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = ""; }; + 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRelativeLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm; sourceTree = ""; }; + 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRelativeLayoutSpec.h; path = AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h; sourceTree = ""; }; + 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = ""; }; 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; }; @@ -1057,6 +1065,7 @@ 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */, 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */, ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */, + 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */, ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */, ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */, ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, @@ -1316,6 +1325,8 @@ ACF6ED131B17843500DA7C62 /* ASOverlayLayoutSpec.mm */, ACF6ED141B17843500DA7C62 /* ASRatioLayoutSpec.h */, ACF6ED151B17843500DA7C62 /* ASRatioLayoutSpec.mm */, + 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */, + 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */, 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */, AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */, ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */, @@ -1377,6 +1388,7 @@ 058D0A6B195D05EC00B7D73C /* _ASAsyncTransactionContainer.h in Headers */, 058D0A6D195D05EC00B7D73C /* _ASAsyncTransactionGroup.h in Headers */, 058D0A72195D05F800B7D73C /* _ASCoreAnimationExtras.h in Headers */, + 7A06A73B1C35F08800FE8DAA /* ASRelativeLayoutSpec.h in Headers */, 058D0A53195D05DC00B7D73C /* _ASDisplayLayer.h in Headers */, 058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */, B13CA0F71C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */, @@ -1570,6 +1582,7 @@ AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */, 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */, + 7AB338671C55B3460055FDE8 /* ASRelativeLayoutSpec.h in Headers */, B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */, B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */, 430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */, @@ -1912,6 +1925,7 @@ CC3B20851C3F76D600798563 /* ASPendingStateController.mm in Sources */, ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */, 0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */, + 7A06A73A1C35F08800FE8DAA /* ASRelativeLayoutSpec.mm in Sources */, 257754921BED28F300737CA5 /* ASEqualityHashHelpers.mm in Sources */, E52405B31C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm in Sources */, 257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */, @@ -1975,6 +1989,7 @@ 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */, ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */, ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */, + 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */, 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */, 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, @@ -2075,6 +2090,8 @@ 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */, 34EFC7761B701D2A00AD841F /* ASStackPositionedLayout.mm in Sources */, + DECC2ED01C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */, + 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */, 34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */, DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */, diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 2bad5cdf..967810ed 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -45,6 +45,7 @@ #import #import #import +#import #import #import #import diff --git a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h index f8413d27..75a48443 100644 --- a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.h @@ -8,7 +8,7 @@ * */ -#import +#import /** How the child is centered within the spec. */ typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecCenteringOptions) { @@ -25,19 +25,22 @@ typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecCenteringOptions) { /** How much space the spec will take up. */ typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecSizingOptions) { /** The spec will take up the maximum size possible */ - ASCenterLayoutSpecSizingOptionDefault, + ASCenterLayoutSpecSizingOptionDefault = ASRelativeLayoutSpecSizingOptionDefault, /** The spec will take up the minimum size possible along the X axis */ - ASCenterLayoutSpecSizingOptionMinimumX = 1 << 0, + ASCenterLayoutSpecSizingOptionMinimumX = ASRelativeLayoutSpecSizingOptionMinimumWidth, /** The spec will take up the minimum size possible along the Y axis */ - ASCenterLayoutSpecSizingOptionMinimumY = 1 << 1, + ASCenterLayoutSpecSizingOptionMinimumY = ASRelativeLayoutSpecSizingOptionMinimumHeight, /** Convenience option to take up the minimum size along both the X and Y axis */ - ASCenterLayoutSpecSizingOptionMinimumXY = ASCenterLayoutSpecSizingOptionMinimumX | ASCenterLayoutSpecSizingOptionMinimumY, + ASCenterLayoutSpecSizingOptionMinimumXY = ASRelativeLayoutSpecSizingOptionMinimumSize }; NS_ASSUME_NONNULL_BEGIN -/** Lays out a single layoutable child and position it so that it is centered into the layout bounds. */ -@interface ASCenterLayoutSpec : ASLayoutSpec +/** Lays out a single layoutable child and position it so that it is centered into the layout bounds. + * NOTE: ASRelativeLayoutSpec offers all of the capabilities of Center, and more. + * Check it out if you would like to be able to position the child at any corner or the middle of an edge. + */ +@interface ASCenterLayoutSpec : ASRelativeLayoutSpec @property (nonatomic, assign) ASCenterLayoutSpecCenteringOptions centeringOptions; @property (nonatomic, assign) ASCenterLayoutSpecSizingOptions sizingOptions; diff --git a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm index 487a7e3b..147142db 100644 --- a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm @@ -23,13 +23,14 @@ sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions child:(id)child; { - if (!(self = [super init])) { + ASRelativeLayoutSpecPosition verticalPosition = [self verticalPositionFromCenteringOptions:centeringOptions]; + ASRelativeLayoutSpecPosition horizontalPosition = [self horizontalPositionFromCenteringOptions:centeringOptions]; + + if (!(self = [super initWithHorizontalPosition:horizontalPosition verticalPosition:verticalPosition sizingOption:sizingOptions child:child])) { return nil; } - ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); _centeringOptions = centeringOptions; _sizingOptions = sizingOptions; - [self setChild:child]; return self; } @@ -44,61 +45,36 @@ { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); _centeringOptions = centeringOptions; + + [self setHorizontalPosition:[self horizontalPositionFromCenteringOptions:centeringOptions]]; + [self setVerticalPosition:[self verticalPositionFromCenteringOptions:centeringOptions]]; } - (void)setSizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); _sizingOptions = sizingOptions; + [self setSizingOption:sizingOptions]; } -- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +- (ASRelativeLayoutSpecPosition)horizontalPositionFromCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions { - CGSize size = { - constrainedSize.max.width, - constrainedSize.max.height - }; - - // Layout the child - const CGSize minChildSize = { - (_centeringOptions & ASCenterLayoutSpecCenteringX) != 0 ? 0 : constrainedSize.min.width, - (_centeringOptions & ASCenterLayoutSpecCenteringY) != 0 ? 0 : constrainedSize.min.height, - }; - ASLayout *sublayout = [self.child measureWithSizeRange:ASSizeRangeMake(minChildSize, constrainedSize.max)]; - - // If we have an undetermined height or width, use the child size to define the layout - // size - size = ASSizeRangeClamp(constrainedSize, { - isnan(size.width) ? sublayout.size.width : size.width, - isnan(size.height) ? sublayout.size.height : size.height - }); - - // If minimum size options are set, attempt to shrink the size to the size of the child - size = ASSizeRangeClamp(constrainedSize, { - MIN(size.width, (_sizingOptions & ASCenterLayoutSpecSizingOptionMinimumX) != 0 ? sublayout.size.width : size.width), - MIN(size.height, (_sizingOptions & ASCenterLayoutSpecSizingOptionMinimumY) != 0 ? sublayout.size.height : size.height) - }); - - // Compute the centered position for the child - BOOL shouldCenterAlongX = (_centeringOptions & ASCenterLayoutSpecCenteringX); - BOOL shouldCenterAlongY = (_centeringOptions & ASCenterLayoutSpecCenteringY); - sublayout.position = { - ASRoundPixelValue(shouldCenterAlongX ? (size.width - sublayout.size.width) * 0.5f : 0), - ASRoundPixelValue(shouldCenterAlongY ? (size.height - sublayout.size.height) * 0.5f : 0) - }; - - return [ASLayout layoutWithLayoutableObject:self size:size sublayouts:@[sublayout]]; + BOOL centerX = (centeringOptions & ASCenterLayoutSpecCenteringX) != 0; + if (centerX) { + return ASRelativeLayoutSpecPositionCenter; + } else { + return ASRelativeLayoutSpecPositionStart; + } } -- (void)setChildren:(NSArray *)children +- (ASRelativeLayoutSpecPosition)verticalPositionFromCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions { - ASDisplayNodeAssert(NO, @"not supported by this layout spec"); -} - -- (NSArray *)children -{ - ASDisplayNodeAssert(NO, @"not supported by this layout spec"); - return nil; + BOOL centerY = (centeringOptions & ASCenterLayoutSpecCenteringY) != 0; + if (centerY) { + return ASRelativeLayoutSpecPositionCenter; + } else { + return ASRelativeLayoutSpecPositionStart; + } } @end diff --git a/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h b/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h new file mode 100644 index 00000000..ddc88c06 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h @@ -0,0 +1,73 @@ +// +// ASRelativeLayoutSpec.h +// AsyncDisplayKit +// +// Created by Samuel Stow on 12/31/15. +// + +#import + +/** How the child is positioned within the spec. */ +typedef NS_OPTIONS(NSUInteger, ASRelativeLayoutSpecPosition) { + /** The child is positioned at point 0 relatively to the layout axis (ie left / top most) */ + ASRelativeLayoutSpecPositionStart = 0, + /** The child is centered along the specified axis */ + ASRelativeLayoutSpecPositionCenter = 1 << 0, + /** The child is positioned at the maximum point of the layout axis (ie right / bottom most) */ + ASRelativeLayoutSpecPositionEnd = 1 << 1, +}; + +/** How much space the spec will take up. */ +typedef NS_OPTIONS(NSUInteger, ASRelativeLayoutSpecSizingOption) { + /** The spec will take up the maximum size possible */ + ASRelativeLayoutSpecSizingOptionDefault, + /** The spec will take up the minimum size possible along the X axis */ + ASRelativeLayoutSpecSizingOptionMinimumWidth = 1 << 0, + /** The spec will take up the minimum size possible along the Y axis */ + ASRelativeLayoutSpecSizingOptionMinimumHeight = 1 << 1, + /** Convenience option to take up the minimum size along both the X and Y axis */ + ASRelativeLayoutSpecSizingOptionMinimumSize = ASRelativeLayoutSpecSizingOptionMinimumWidth | ASRelativeLayoutSpecSizingOptionMinimumHeight, +}; + +NS_ASSUME_NONNULL_BEGIN + +/** Lays out a single layoutable child and positions it within the layout bounds according to vertical and horizontal positional specifiers. + * Can position the child at any of the 4 corners, or the middle of any of the 4 edges, as well as the center - similar to "9-part" image areas. + */ +@interface ASRelativeLayoutSpec : ASLayoutSpec + +// You may create a spec with alloc / init, then set any non-default properties; or use a convenience initialize that accepts all properties. +@property (nonatomic, assign) ASRelativeLayoutSpecPosition horizontalPosition; +@property (nonatomic, assign) ASRelativeLayoutSpecPosition verticalPosition; +@property (nonatomic, assign) ASRelativeLayoutSpecSizingOption sizingOption; + +/*! + * @discussion convenience constructor for a ASRelativeLayoutSpec + * @param horizontalPosition how to position the item on the horizontal (x) axis + * @param verticalPosition how to position the item on the vertical (y) axis + * @param sizingOption how much size to take up + * @param child the child to layout + * @return a configured ASRelativeLayoutSpec + */ ++ (instancetype)relativePositionLayoutSpecWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition + verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition + sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption + child:(id)child; + +/*! + * @discussion convenience initializer for a ASRelativeLayoutSpec + * @param horizontalPosition how to position the item on the horizontal (x) axis + * @param verticalPosition how to position the item on the vertical (y) axis + * @param sizingOption how much size to take up + * @param child the child to layout + * @return a configured ASRelativeLayoutSpec + */ +- (instancetype)initWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition + verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition + sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption + child:(id)child; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm b/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm new file mode 100644 index 00000000..7a1a0ea5 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm @@ -0,0 +1,117 @@ +// +// ASRelativeLayoutSpec.mm +// AsyncDisplayKit +// +// Created by Samuel Stow on 12/31/15. +// + +#import "ASRelativeLayoutSpec.h" + +#import "ASInternalHelpers.h" +#import "ASLayout.h" + +@implementation ASRelativeLayoutSpec + +- (instancetype)initWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption child:(id)child +{ + if (!(self = [super init])) { + return nil; + } + ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); + _horizontalPosition = horizontalPosition; + _verticalPosition = verticalPosition; + _sizingOption = sizingOption; + [self setChild:child]; + return self; +} + ++ (instancetype)relativePositionLayoutSpecWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption child:(id)child +{ + return [[self alloc] initWithHorizontalPosition:horizontalPosition verticalPosition:verticalPosition sizingOption:sizingOption child:child]; +} + +- (void)setHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition +{ + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + _horizontalPosition = horizontalPosition; +} + +- (void)setVerticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition { + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + _verticalPosition = verticalPosition; +} + +- (void)setSizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption +{ + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + _sizingOption = sizingOption; +} + +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + CGSize size = { + constrainedSize.max.width, + constrainedSize.max.height + }; + + BOOL reduceWidth = (_horizontalPosition & ASRelativeLayoutSpecPositionCenter) != 0 || + (_horizontalPosition & ASRelativeLayoutSpecPositionEnd) != 0; + + BOOL reduceHeight = (_verticalPosition & ASRelativeLayoutSpecPositionCenter) != 0 || + (_verticalPosition & ASRelativeLayoutSpecPositionEnd) != 0; + + // Layout the child + const CGSize minChildSize = { + reduceWidth ? 0 : constrainedSize.min.width, + reduceHeight ? 0 : constrainedSize.min.height, + }; + ASLayout *sublayout = [self.child measureWithSizeRange:ASSizeRangeMake(minChildSize, constrainedSize.max)]; + + // If we have an undetermined height or width, use the child size to define the layout + // size + size = ASSizeRangeClamp(constrainedSize, { + isfinite(size.width) == NO ? sublayout.size.width : size.width, + isfinite(size.height) == NO ? sublayout.size.height : size.height + }); + + // If minimum size options are set, attempt to shrink the size to the size of the child + size = ASSizeRangeClamp(constrainedSize, { + MIN(size.width, (_sizingOption & ASRelativeLayoutSpecSizingOptionMinimumWidth) != 0 ? sublayout.size.width : size.width), + MIN(size.height, (_sizingOption & ASRelativeLayoutSpecSizingOptionMinimumHeight) != 0 ? sublayout.size.height : size.height) + }); + + // Compute the position for the child on each axis according to layout parameters + CGFloat xPosition = [self proportionOfAxisForAxisPosition:_horizontalPosition]; + CGFloat yPosition = [self proportionOfAxisForAxisPosition:_verticalPosition]; + + sublayout.position = { + ASRoundPixelValue((size.width - sublayout.size.width) * xPosition), + ASRoundPixelValue((size.height - sublayout.size.height) * yPosition) + }; + + return [ASLayout layoutWithLayoutableObject:self size:size sublayouts:@[sublayout]]; +} + +- (void)setChildren:(NSArray *)children +{ + ASDisplayNodeAssert(NO, @"not supported by this layout spec"); +} + +- (NSArray *)children +{ + ASDisplayNodeAssert(NO, @"not supported by this layout spec"); + return nil; +} + +- (CGFloat)proportionOfAxisForAxisPosition:(ASRelativeLayoutSpecPosition)position +{ + if ((position & ASRelativeLayoutSpecPositionCenter) != 0) { + return 0.5f; + } else if ((position & ASRelativeLayoutSpecPositionEnd) != 0) { + return 1.0f; + } else { + return 0.0f; + } +} + +@end diff --git a/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm index 10c129f1..f6ba9da1 100644 --- a/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm +++ b/AsyncDisplayKitTests/ASCenterLayoutSpecSnapshotTests.mm @@ -38,10 +38,14 @@ static const ASSizeRange kSize = {{100, 120}, {320, 160}}; - (void)testWithSizingOptions { - [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionDefault]; - [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionMinimumX]; - [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionMinimumY]; - [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:ASCenterLayoutSpecSizingOptionMinimumXY]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone + sizingOptions:ASCenterLayoutSpecSizingOptionDefault]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone + sizingOptions:ASCenterLayoutSpecSizingOptionMinimumX]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone + sizingOptions:ASCenterLayoutSpecSizingOptionMinimumY]; + [self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone + sizingOptions:ASCenterLayoutSpecSizingOptionMinimumXY]; } - (void)testWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)options @@ -51,14 +55,7 @@ static const ASSizeRange kSize = {{100, 120}, {320, 160}}; ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor]); foregroundNode.staticSize = {70, 100}; - ASLayoutSpec *layoutSpec = - [ASBackgroundLayoutSpec - backgroundLayoutSpecWithChild: - [ASCenterLayoutSpec - centerLayoutSpecWithCenteringOptions:options - sizingOptions:sizingOptions - child:foregroundNode] - background:backgroundNode]; + ASLayoutSpec *layoutSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:[ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:options sizingOptions:sizingOptions child:foregroundNode]background:backgroundNode]; [self testLayoutSpec:layoutSpec sizeRange:kSize @@ -97,14 +94,7 @@ static NSString *suffixForCenteringOptions(ASCenterLayoutSpecCenteringOptions ce foregroundNode.staticSize = {10, 10}; foregroundNode.flexGrow = YES; - ASCenterLayoutSpec *layoutSpec = - [ASCenterLayoutSpec - centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringNone - sizingOptions:{} - child: - [ASBackgroundLayoutSpec - backgroundLayoutSpecWithChild:[ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart children:@[foregroundNode]] - background:backgroundNode]]; + ASCenterLayoutSpec *layoutSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:{} child:[ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:[ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart children:@[foregroundNode]] background:backgroundNode]]; [self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier:nil]; } diff --git a/AsyncDisplayKitTests/ASRelativeLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASRelativeLayoutSpecSnapshotTests.mm new file mode 100644 index 00000000..468b515a --- /dev/null +++ b/AsyncDisplayKitTests/ASRelativeLayoutSpecSnapshotTests.mm @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import "ASLayoutSpecSnapshotTestsHelper.h" + +#import "ASBackgroundLayoutSpec.h" +#import "ASRelativeLayoutSpec.h" +#import "ASStackLayoutSpec.h" +#import "ASLayoutOptions.h" + +static const ASSizeRange kSize = {{100, 120}, {320, 160}}; + +@interface ASRelativeLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase +@end + +@implementation ASRelativeLayoutSpecSnapshotTests + +- (void)setUp +{ + [super setUp]; + self.recordMode = NO; +} + +- (void)testWithOptions +{ + + [self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionStart]; + [self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionCenter]; + [self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionEnd]; + +} + +- (void)testAllVerticalPositionsForHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition { + [self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionStart sizingOptions:{}]; + [self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionCenter sizingOptions:{}]; + [self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionEnd sizingOptions:{}]; +} + +- (void)testWithSizingOptions +{ + [self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart + verticalPosition:ASRelativeLayoutSpecPositionStart + sizingOptions:ASRelativeLayoutSpecSizingOptionDefault]; + [self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart + verticalPosition:ASRelativeLayoutSpecPositionStart + sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumWidth]; + [self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart + verticalPosition:ASRelativeLayoutSpecPositionStart + sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumHeight]; + [self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart + verticalPosition:ASRelativeLayoutSpecPositionStart + sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumSize]; +} + +- (void)testWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition + verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition + sizingOptions:(ASRelativeLayoutSpecSizingOption)sizingOptions +{ + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor]); + foregroundNode.staticSize = {70, 100}; + + ASLayoutSpec *layoutSpec = + [ASBackgroundLayoutSpec + backgroundLayoutSpecWithChild: + [ASRelativeLayoutSpec + relativePositionLayoutSpecWithHorizontalPosition:horizontalPosition verticalPosition:verticalPosition sizingOption:sizingOptions child:foregroundNode] + background:backgroundNode]; + + [self testLayoutSpec:layoutSpec + sizeRange:kSize + subnodes:@[backgroundNode, foregroundNode] + identifier:suffixForPositionOptions(horizontalPosition, verticalPosition, sizingOptions)]; +} + +static NSString *suffixForPositionOptions(ASRelativeLayoutSpecPosition horizontalPosition, + ASRelativeLayoutSpecPosition verticalPosition, + ASRelativeLayoutSpecSizingOption sizingOptions) +{ + NSMutableString *suffix = [NSMutableString string]; + + if ((horizontalPosition & ASRelativeLayoutSpecPositionCenter) != 0) { + [suffix appendString:@"CenterX"]; + } else if ((horizontalPosition & ASRelativeLayoutSpecPositionEnd) != 0) { + [suffix appendString:@"EndX"]; + } + + if ((verticalPosition & ASRelativeLayoutSpecPositionCenter) != 0) { + [suffix appendString:@"CenterY"]; + } else if ((verticalPosition & ASRelativeLayoutSpecPositionEnd) != 0) { + [suffix appendString:@"EndY"]; + } + + if ((sizingOptions & ASRelativeLayoutSpecSizingOptionMinimumWidth) != 0) { + [suffix appendString:@"SizingMinimumWidth"]; + } + + if ((sizingOptions & ASRelativeLayoutSpecSizingOptionMinimumHeight) != 0) { + [suffix appendString:@"SizingMinimumHeight"]; + } + + return suffix; +} + +- (void)testMinimumSizeRangeIsGivenToChildWhenNotPositioning +{ + ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]); + foregroundNode.staticSize = {10, 10}; + foregroundNode.flexGrow = YES; + + ASLayoutSpec *childSpec = [ASBackgroundLayoutSpec + backgroundLayoutSpecWithChild:[ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart children:@[foregroundNode]] + background:backgroundNode]; + + ASRelativeLayoutSpec *layoutSpec = [ASRelativeLayoutSpec + relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionStart verticalPosition:ASRelativeLayoutSpecPositionStart sizingOption:{} child:childSpec]; + + + [self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier:nil]; +} + +@end diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testMinimumSizeRangeIsGivenToChildWhenNotPositioning@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..02717f8fdb7afa87892b61d4e4638fa5ea022138 GIT binary patch literal 4955 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV07SM1B&RJJqwaxEDmyaVpw-h<|UBBS>O>_ z%)lU~3c`$@K`I}Bf-;#d{vkk`35Y>p0T63IX&{>`X7S`@Kt7j-r;B4q1>>6=2YHVe z@Gv`mzhA=i|1g_b@d42VjHz6f=Pmo~nI(UmJMdB2p@D&s$(8Zw|JXk^DbP0l+XkKb|Z(7 literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..50cb613c24dffd7fe74cd067e3a7829e97f311fa GIT binary patch literal 6221 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV07SM1B&RJJqwaxEDmyaVpw-h<|UBBS>O>_ z%)lU~3c`$@K`I}Bf-;#d{vkk`35Y>p0T63IX&{>?%c^*aIgmEGSU5_q!NUO2h)L-qXmEGiR(e%$;0C-ndWGmpWBvixFzTHCf6jXPk`g@Hd)4;m97BFcvPHIrh^e_VH(vb>aIQ_|g zz0Sx-(q2GYHNL90PXgHvKsn+lM3;J_kA)(txUt>4G^%_3;0{a&u0b?S{r zz2EyxlLYAodB6+k0S~E3NFI2!X2& zlDbDz+GzH~RgR99X((A`v?4$*M@MVV(IyAZX3l7n139aVHaSqrQH&->J+LG3|NpTo zmU|c&xH~{Sh_^R)PfRuxV0L&qf6KSzVgZH=TTA^(^wV!Yrrz22X2l@&TqZO)UxJi27l0ic zp6Q?t&eS-cH8;A~T846Xb|SH)gJc9e>L2?OgN4 zi=ZF&$F=_1H`(Fwvh8Zq5nUVwl?M|_AgSF1*sVJJ6qwpmmxdGe!&j{*-{-POneR2- zc?J|9py&z!Mpye1XXNObTdt?Qm#`n!hb!)XuR<`ofc>w?ms1J)p&rlB zp7vT#*bk#gZZyde$~L1(j%uackG&^b6_~-LERaYz2^xy2pMD^EHS;`BJgTe~ HDWM4fnCrt8 literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterX@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..69e7392384b2717608d347dfbbf0aa940f24a3e7 GIT binary patch literal 6922 zcmeHLeNYo;8haY z@^A1aup(`@0f5jA+(Oc`Ua-N<$kLRIYGsi5>8l`}Avp0h(o<|#MdB5WgJ}Vob7p0EJA3ROErLuQ(u zgo2nGK{f(>>Jk|Wn&Vs6i3Fj7b8(3mGYXk{9B9Bp zfxN*(n5;3kG0y(JSQN-J-Mhfa&7u5H&(NQcSI6%J8Os-OfV76u8m!S?*~Ft;MVL$P zSPjr=eRD*Q(TSJFw8vBw4V*ANkLo!gns}1Js zP`prwcOOH1qwG&%e+v6k*k6VHRoGvJ{ey&eO7ag9%TtnnkXRn{{j>1$l=MH%!ju^R zz_Ne;%JnfkA5?P(!AEy5louxAf}3u(W#LkEwx)UG4AHK=w^5<5ixVz7oyKa{XPBgG zgnrPM6yn$~m_IPFq`78aDq&0>{qXxd>>3-KERt88LbBR; z9}D+bR=d@&MxpF0JTXa9Gx)D+Ef;@Uv7~k^&Kq&DhGpBr7(RMDRj?&Q9vQd>dz@f< z6!?@)Gq)XH24`C0-PH+gG(cXU>%!XqTy)x<`g6|q8*$FmBl3y^2rmr;5+pTZt9(a* zJ0eisk@rqYWF+-n62j!RX*0t^?qqB7O6CQLorV`lKAilQ!H2ZD_>{H31z@$7p~sVy=Z3m^zb25{Ui5qr?%Vc{n2=%bT? zzTo`vP}HfiM?(@%l&Vd0#}m%(pxzX`*l7GJW0^8M;Yse`=Hj<$(xPCdWBTe4o-<`Lk|4nv zc+NzyEj`xVA#z5KS0xVqI-i6JSw5++$Fv+8F`c;?HZ?hV!WS&V$`lRe^=6L5Cc`~t zM&eG+aYX)rYKf1kphJB;=XbN;&Hilm7mfeBqERZA&;XN@)N?4m0sixLgS$a+Le>kA z!-DdmnX>nlO^KepZrI;=B{Mz$g>aXH>ItEOdjz1bavkQ<4|@UXF)A(~K`*a!`iG9K zCx#+Dd%BenW)-lM9rWdK_a7b-(9R)+1{6>e_IKm^^ZgSYc#o0UW))#z*ZYqk*m`W{o(Sk$7M{>3J6;z zYMXfHkVmWJA=Dq(`o(ImMUs-Be0gZsOXoYM2lm|e2(&3zKsc@-jl@(e2+8*bbKgqA_os(HJ^bkb>x*MmLF7-0 V4ZlgtghvvP{`y-f=aPwF7(TbRz0z`t4O*y(icUp0gcdRyqSBTzgiWnNb($Ku0|}T6=m><#w=Ol*T#Uwk6u3p(*lFdAQ47O1jo`jI#xyz+V0%j%9Qoj{+Zk+C#UE1 zIVbP?-1olcJx^)Kb=G$k{?QB3^;O0f<_H3yVhUgq(y$CmzB|O%^nKk<4-uTOeU5O( zc#nc5z9B&Ka3OV_DymGB4EK#k}PeEy?V1 zfU(rEVI%pUuS|%pfcEd=B1A?DW0l`IGC#`;%~9 zugBZ?cXPB_ab~@pxTbu%URqKUBbb?(d92Y9QUQ@V@i=Iy7C5j}G{7~$V1jlp!ls8>(s`Z0+nBzQ^2__1bP*hF$ zwY&KjJZZ36Iw#l~Go4l^(H=uM>3~m=hAv0xZXWJhie}dxY!FA0hG98k)0mVyB1HXa zMNXlUm(;3bYO9yvYNZArvHs8quqv}JCCR3oYHM9fd0*aNpbV{3eU+| zJ~ZM+=Sj<^(X9v6M3$jujn%mbA0+6TUL1*FiTA8~MG{*HUY7Lm(_9jI=;e|nCov_B zMk+4$hK~$(pM4b^g%wFp>EF`RM#So##;cODXyS<62QoJol|e#bRr-=SmS&b_Hk#R_ z!6pqhHz$hTq$4nb>yts7I((XplvY@w0#+?mfIP#%#9_^a&ip!NE)M0M0#9Br8tfdx zyp*)Ir1*qW1r%n}K~aTIxJB^$@Q{SM?4es5RL|l&7%|rv2w@GON;E59wc}qYHWzA0 zdu%N?hw7tsXY*)jjX_qxCMd(okI zV-iEz%|HlGF{m66_#fux*F{|+q^F(JIy%|1>8vG^aWFd&LN5d6qT_xQetDpW&xXev d7#@^7wrW{H&9};j@b4Lr@&5MID=Ea$e*m2=yp;d| literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXEndY@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..385fc3e81798df41a041666071cadc2b97ceda14 GIT binary patch literal 6474 zcmeHJZ%k8H6u4a`6LoAFG0szFB%hR#}Kq&wajR1$1xXw=Y0aBEmkqYX&qW^$DLiEem z7Xc7Ho1I8z_IplfjLuJ6y%w%0ob0HBQyy61ci}6ds(BM!TQbv9)|McHy=OkdQV3qk zliI}D>7ZrF%8C!~+(Ji(iW%3q;flYLLU0Mmems+Sa*B2Linre-lr+EyrBkN?FZs74 z$iRlLq@h47y6a0!4j4iKUvsKF@c&P69^HHOy!vkgPlIF)XeW<1S0ae=g8tO%DFcFJ zf!m{#7n8Cr8t~2g8wPGo88jdaoLB$*+R<}U2JTue$Rx#4ihF?q^@A%HTycbb=5;y{ zitow$+%(5I6e5^*6~WwM0cE~n|JuN$*A&P3tLQE0cXMq==MhbXEXj-rA?e-V-?I&! z?{+J8CY@_-r-0<5Z!WLKo-9v`oYA|IuF^9r8?;H`us7xb3|~}XwSQ0PWzkB#ePVBO zn>(*B|N6|bkWc3*zZia4b(J2&VXy3tpeb`_|4`!`#|{&-qj70cHNk>0r8>Ohds9y= zFn98*L)@dsyvQ?nwDXfg;*9txjf2>+2{9D z3bYGCu{Cz4N2+_HThRF9v{|wFuFT@IvObr~>+>8p6HQ0Ww(=Ohzn(HWN7N35$&}z6 zpKih9ox5;*E%mUA@UsMgR?X##bJg z08Cj-#~t3b>#-P6X8G5F>V@IF6UXQusOIiok)dFW07zW~t-+5B@2o)SIx+4m^t=hM z{=&+rR)d$wmp91uo7=05GqFO}5siW}6LI}R^Z8-ED84O2oeRA%4_i^kPiJH@7RBI~ z*K!?24|G5Pnx~ut_;#hk)M^3}xvBM`j3{g04sr%A%liw+`~vjYgaT|2Ri>>gIilH>Xw43UKW)*}Mlx3m6 z%<^!W0t3+u)L!quO^l7~J#TQ{GNt}=f2z^cq2UOB)X<@Tze(Bq~#E%}m P0&rxeuS%;=B`tpe;{dF1 literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..28036afad5bf216b5f09517165fb1054933336bb GIT binary patch literal 6055 zcmeHLeM}Q)7=Q1MLahZ!6fGzU2AN@Oln-SCgJ(shW3Y~aUpOd{3F<&HabQRF>JYO` z2GYb2kWnVY4}!S4i4zLlNKl4gMe$?E$L1U$I;xZ@AG3Sk>-CngCjD#4vRsqv-M#m_ z=iYnod7j_%{63LIuW=N(2><|&(unXF0B{NbjO5$H5#P2pbOGBLvql2Ww$B@cC(|+_ zHtYmo_9C=l(ikrT9Gtf`{H<8H$Ki&48n`WH_V9Rm;o^<1PvL%(G<-#DHumUF>!EOe zK0)@E8FO$Fi+DHwqhlqz)13E|Jqvr$Nn!RPIr_WfZ^(I6TJSi2;+9aX1B6g)2=)L9 zY!ttWPTmw;N5(HO-zz-m z>I^Q2U97%#>oQ{w*MQWa&F1H;{0Y48jzjI}U5hyq%mQgjW+vh-B$zkRcOi^5$ER>j zbpbQ?W+9*2yx`ug6pJ|wlYy^CnOW+NVpztbkGsa-x0v$)0*albD9{6Zrp$;hW6jx- zn4X;_^U_1-E#|nF=Dsu?F#o1|PySr6+(o9U!iy`sTTr~{Q(-_*aUQXnCmo^cq-Hec z0rGy@s}+nXPyQVS{t0UwX@5|$ju#}2Dnc=%xsJT}40(#YUzz3IbP$b^K-gqbxICSR zwh7zYU)n0I5u_Fsd!gtiPoQ{3NOd=kMtZ92GYp{6AMj4aaV>zZNo~lUVTaHqmgbaqLW9yJq%|VPr`Ha z(+0Y3p~rB!=*8`-!rm;~u+ZU6(TX@yebdVoeRMZRlz0TWf}jLNO2Ab*_1WpflQru+ z8;4_U!m@h4J#IWhd7qXBpbQ%1hJ46{?~+jx7Jm*S~!kWb&xm+r7PJ=Lv+{#1K^Dsr=a_Qe55xSj<#*R1x_H5~2<>#l~Xr5H6eN)(CFdP)!2MF>cv7Z=)-7 zE=alD#!&)lwX85{UzU3NzWHXk3wC6Ys9AhoXO~cIzYKM;fH0ZV4mh7agnzms%#TU{ zT0qkbJU2^Ae%sq=lMB@NFPbWLiWsMuSVd)P1Tm?QIoq3GWKO@$b>1ol8b)U$;g2WAvpX>A~A}m88%DVPBdxM0aja zu&46+9D1sYkZdq8MYn6ntb4nvi}{v2tRa5$Ha1Kf{Ir4g0beLm^2l0?fN7Qn4V7K_ z8@qp{`+rgPTq}r-;PPit5sAkAXwl>?)*OIyktq)FpKy^qKvC(Qav&^tE!59}bX9ct IS&4k#pTAf@2mk;8 literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..692c2a4e84f552eff3e4bf5b1aff535e3e29217c GIT binary patch literal 5710 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV07SM1B&RJJqwaxEDmyaVpw-h<|UBBS>O>_ z%)lU~3c`$@K`I}Bf-;#d{vkk`35Y>p0T63IX&{@MciEF~+zbp{H$7b(Ln;{G+&IX~ zY{uJA@Msv4**`119Vwf$kR*y9i1+0+=978V+T%?U71V4QMb zM#H7+-tYhL7=y%m4&VPhon^|gqbDI2-ECkEU`l54ir3%!+4u}d%z&@{eY{YBl(;&` zB@E1^%wi6lJ)9b~d%v7JZvYZIVDV>fy{AKF%pABw7%(03Pj%EGs8KT-BIt>AG(<*2 zWV9R^Em=qFn$g;Kv^_F3TWIz34)8ND{QqyGn)M6V$bANC z9l)R}pqB9BcKbbOlUkte&)s^@0~ahT!1hXHm@i=PQt&x&VLSic=jk&*4pjK_WB30_ z4GRkkAWioRc@B(I9A-2u%$Kh^4{3Hg{PP4ZM%IEit@^pO>_ z%)lU~3c`$@K`I}Bf-;#d{vkk`35Y>p0T63IX&{?BW42B59UyJu>Eakt!T9FJ?m`wr z9+!h>?K5kgDxL|%YE@=`XyX-n@cnbG{lC-$49q+RAmWbUhnsJ#XXmbeYYG+lBYlCP zit#Jc8K{!uJQ)oAxjRmte{viuBIUrlU}Zlen?ynbBU_*31qQj>1;O*-A|?$|4#DzJ z(`1YnFxqT;xXcbNlEGlwu*?pkS;8XCfw>~*ux~9~;g&t%Bxxxq_{Vji1O=?(FSYQOsDI4+}m^B(_G%SkOuX>$61C%=y;{H8ddSZLN z+JX$3Igs4A23Rn7DX1-YalQNAFUd5J@g39u&(4pk3-u&quK9m?j6tqAa{B*SbKU)`R0!BuU#a=&?QBjBDRy;88S@&LjfFZmHGX|v zw7sqk?C(cU|38~OEk0C;fQ|e2i2VF^mPMquc`~F(C}oy%;5@=2^0(?@wEbbQzdwGe z|Ll8uy%r%GB`Om*5GeRn!yKM8W+jF{bmNaiq{8|jXUUN<0$0EZ3+^@u z1~4TudEJkL*R3{x_BMJtWadCC)-BQoz>3w!;pJ^_SY_DKzW=*Er$%otv|cJ>W^>?d z;nb+DehI5fK79E8PnB`$#zIIPej)DwgGQqP?n4VP{}TND}Q2@F#l zW;9&dJ{{Hy;NY)+A1@RjB@L~R-?H&E2yqAp{Q7nYR*61*`2MdqlUMR%NPPwj6h6fm?>p6P)1YAX!aQ`Q-B>5niRAK2M$bXeBXMIeLA#S<1yHfa)2R&p>Fzt-)qe( zARV@U{}UNl1fb(C4UMYIaSVHb!$gqcyrI#MneD(v{(U98KStku2I+e40M<6V4SBGE z8-uh13>M!G-fej|dqT{UL!d4a#F~=Vu<@8tLq@~IU^JZoGdkV!&I4w>HraIVX!V=G Pv1A5MS3j3^P6O>_ z%)lU~3c`$@K`I}Bf-;#d{vkk`35Y>p0T63IX&{>`r`2m(3y`+(ba4!+V0?39_rxO( zJkE|!=YOenYB*UL8~*&{g-NzT3*JBfUH4};BQuY|0R|v(!SF+5S^CYg-0M(*5BUxZ zUs$h*C$LE*G%&JBbVv#?By(T?V`2{#5MW?+SlbVj0ZAvZu^hN@XsaDufW?6^;B_)c zV?smY3`RzSZ3S!nVFHYd0Ss4uLCj51NONH5(T#b187APsAi}WK4%OOUTVMjC){X}9 zXp$PuK%>P4yi6V~94V=RiqtnWE!ZtlZgM9Uw8S_>S>%7CUSEi zm1&H*0>i8W7L4uxel0Bj&tnX#aeMUtKlPrNeSc1a^W%djK~?a91EtJN3pjg(3tp_3 zulj!804(<7Q~l@J)8e=K99a1AK?=A+eXxs>@dC>+PKz(s`S<=#p8;}nLd{S8_^z1W z&sb!n`28T2=Q?Qt2Hymr1@(U~?5=MEyZPAk|FiFJ`LimOiLaMk9a8&W=jCWnGw?gG zaDUy4yY`2{Zhm~a{%6pWTjszps3 zr{TJUL*?{zrR)1F8QYIFPlg3DivwmL|9@t#yFY6t!BC0cC$jU~S;B#AzCZJ4=xM^C z@@eUbt@*$(AP~s=fBK$YAEibxRHo~@uB$C37|8$6=10|CH6j=)^htU;!arCU82X{N uSx4)d(b^Z>U>|Ld@bnC^7Fzwh1N?ezvgvPM=>wa(3=E#GelF{r5}E*4X}v7~ literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7011b35abfc1c05b07a53f54ed35d428b20e78d4 GIT binary patch literal 5894 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV07SM1B&RJJqwaxEDmyaVpw-h<|UBBS>O>_ z%)lU~3c`$@K`I}Bf-;#d{vkk`35Y>p0T63IX&{@cV*UiDpFrBh)5S5Qg7M9b(}hPI zc$^)d&fj9s$isZ#b*w~s~3m4#MU|MkP zI7lN<+K`zkA?IP#e7FG90)`b|4Z-Ffa5&SzAQ8Re+H#mc1JFZRwW!v9&4dYzT00uZ zqe*Hs1C16J@G^O{a3sA3s{eR=KIe=B*-U(%?JA&J>cApK#sCJh#$={${k`c$Gr_eJ zN5k=_|9@#c`F@T?W)hz#sIqKyWd>%x45s5OV)0eypBN>Bs!sui{?q$E&pkbPpW1^7 zB_2kgnoUPafPvLP*g|c?k4x2R(tpwJ)lM=uuaLdg28)%N{I`A|<2Q?-o%VaVe%7fs9`%0jGYyi1fgbPz zdcZ?!5|Rhr?H+O^*7irLyU|w);eY{Rjq6 LS3j3^P6y-%W literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..50cb613c24dffd7fe74cd067e3a7829e97f311fa GIT binary patch literal 6221 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV07SM1B&RJJqwaxEDmyaVpw-h<|UBBS>O>_ z%)lU~3c`$@K`I}Bf-;#d{vkk`35Y>p0T63IX&{>?%c^*aIgmEGSU5_q!NUO2h)L-qXmEGiR(e%$;0C-ndWGmpWBvixFzTHCf6jXPk`g@Hd)4;m97BFcvPHIrh^e_VH(vb>aIQ_|g zz0Sx-(q2GYHNL90PXgHvKsn+lM3;J_kA)(txUt>4G^%_3;0{a&u0b?S{r zz2EyxlLYAodB6+k0S~E3NFI2!X2& zlDbDz+GzH~RgR99X((A`v?4$*M@MVV(IyAZX3l7n139aVHaSqrQH&->J+LG3|NpTo zmU|c&xH~{Sh_^R)PfRuxV0L&qf6KSzVgZH=TTA^(^wV!Yrrz22X2l@&TqZO)UxJi27l0ic zp6Q?t&eS-cH8;A~T846Xb|SH)gJc9e>L2?OgN4 zi=ZF&$F=_1H`(Fwvh8Zq5nUVwl?M|_AgSF1*sVJJ6qwpmmxdGe!&j{*-{-POneR2- zc?J|9py&z!Mpye1XXNObTdt?Qm#`n!hb!)XuR<`ofc>w?ms1J)p&rlB zp7vT#*bk#gZZyde$~L1(j%uackG&^b6_~-LERaYz2^xy2pMD^EHS;`BJgTe~ HDWM4fnCrt8 literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png b/AsyncDisplayKitTests/ReferenceImages_32/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumHeight@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b14c267b42e8fb5a3472b8912e8576c6fc377a19 GIT binary patch literal 4847 zcmeAS@N?(olHy`uVBq!ia0y~yU}^xeKX9-C$q5BlPXj5&;vjb?hIQv;UIICs1s;*b z3=CqbAk63)r1AkMD3j^p9|EMAfEWZSfLH@c1KFIZ*Rw7y1=5C|E{-7;jBjon>}59O zVRra_|4ZCDhD{7v0#7#E3nzDesGdKcMP-7}k9)uWq#j^k<}uii_F((HKPn21Qx4o} zEtm7|VPum?cyPagX$6D#0+oaSsVO`LK!x3GA_q48xx9M9`*NX(JzvhH%rpdP*J$8c zz~aT!wnXtH*su%-MxE3D&sk5Oyo^Omf3I<58dx{i0w#^dNezmb9!4NtI#K})r$70x zciMN~GvVVU>&dCdK)M$&Xf+6Fa7s*SQvvc099YE2x4JE6LRs_i9=xFUpjV8wjb_Rz3|4*94iU1o=`k=#?2C|taPH$P91Ega;T^vIy7~fnu$je~B z!?f|u{t_$4yv8V-pOH;7MVVOq8aErrFtL>Jv@kRZbeyUeP)JZ<;b?IPx!26V$gJp~ zz_RF*91Dj82P2cBz|`*w4hI}K1UMR3>|tkQVsmP6;Ba|r%OPMOz{KLDp!MFdp`lSg zL4YZ+lAnnMYGn1Ok)vTb3=`3V>PB`3hX4QdHNW3sU|?|vWwf`~HcmVoAaFR~>-{Zn z3Ud}U))p6^yw9WLVSoNLKO>7;W2FKg6UQEYM*#-~jw7D+3JnZUX7wl&93+D;91i@p zSzphS@}Qn=AM>aG4`jt9fBfAi#Ns%iaVjHslH=baeLMmx57vZztV=KCa1!7al2q_G zuu-IZT2gIxlfaQC7d8%|g0R&U`#0;bD0Z-Rf>cLzS$l8XXX?n2u|$WPkVGOXidJaKvTqo6AX?_=dwiKmrrYj zsh06BOxL`&U5&$0$0MadfiZn+*LUmFPB7JdOLuG*c^xg>q9CSX;t+DLnSqg6(LsS_ z(I+_;4harMCPjg%-xVAVIB*DXG_Kgg&d9{()ZoD3^3;|?z(9bB#YsWyy<QN&{!xEH;_KAZM>Hq&9*LMg2Q)V|PWxgFvm!r97fQt=ay5#yg?@mc+ SygM*`GI+ZBxvX;Vkfo zEM{O(v;bj7*J*|cKtY*I7yl3-%>=|C@BxT5pfr#znrb*Js0&CNdb&7HIBxM=z{@c$h>1DlwXg&hlrfPzB<10xel*MUQeugmJD zOIBHCa zrkU_&95|u|F&Zd3fkj7j8W(Ry!qP^QH!=wE4WB-g`g_k}Gf7AihB|~pz=LTTmv<|h z$%ab~Qe}Jy@rcSFyOyi^GD{h3M7U0c(Ytl26SGuArUI{ZJ3@SoP0iiQla?{^&bWc> z=B0;%7Bcd#F`3Xd_b@^{Om5%1OwY>=Y(6_xy%Va_0 zRJKbSif6#Z51cxD-BveK>Ow=OFFZIEI3u)lL`Q=eoE%4kc{DW(D2x^wqlLz3*^H>_ zMytTlmIg!PXiHAq(7ea`FC4#2_2j0Zp9jM$6N|k~Hl|8jI zzUV26N<2B57@DAP6?_#cTidEg;V#9Zwknra0ShV-q=LeB7I6341d{us=|6MHKJMmb z_ct@&eDnLh-|UOgN4UzS$^Zbak-G3$03<8`n0=fS{*wBb*b6V%w%CXTpuBO~6ZnNp z*TroEz+*0dV2cX^Y{M0)|l8 zboQ1#jX|DuZ;RAj0W|0x4@jM|fcZT(VYmWc>i{N6Z~e1ndr4&!nc!uamf8G4l$4?w zZ0IDhU@LLOO_jOo=;V>Ns9AO`3r=0>UwyT#?;cX8XaV8Xzn0afE+bichE8lNe*%a$Q2-0fLsA` z1t@$&(I^V8QLX@Ga!_Il<=If$5JO2_l%u9zLG6L#dUsc80hsbJFFHK^H7wuVUruN_ zuBmbJd?oE3GCOYER?T{9-m#MSTW#GhBXEUL8-cf2+d3-bf<_=6OtmKmKly@CDGm9= zVE>nujYcuG|0>W7f`O4CQWfgU@(%@R!SDlvCse+Cez~N3rl*-&%*7K^$^f zi9w+Uj%$_N@89EET(hG4fuddKOn2wra5b%d-%#h&xJExL>)T9bsGRBU+CTCdD~_`3 zof=o~=fCW@LF$(wMbBbr8N zHDX3s23n2y#UHIkh}21`5f1%-lRDO#xNMw8*v&J{i0^LEVws@p<@lXkXomG}zy^W& z({_$qMqq@%GlyumVL*BxRAZTvpM;G@Jh4BeHiJF8@Nb%G21>I}f1NCrc4>G@EmI3I z179!w8iULG$@Yo@WpS{d7=zGsEe1L6az@;A1YsH`NGH;^Xl+)}C2qDgp!c9yng~l^ z1qaj+zaFynuuyE^Q^ligrJsp3UEVoo3@rNY-s0Bm>!HN-@W5ml~IUA7}ztTV|kGBaIh)lAN{-CjgPooPKZx{cXQWhG3fx&b0^YhHBA;{ zCbgwy3@pJ7zvULSx#e6Jk}4e}wyj=e4I-TedEANy;0o7_j= z=e@VjJ@>rN^LwA)_2cA&i2;7k`5_1rkfe)GK@i-EAn3R+0a~*DkeI*$%}+_(hcxwX zeF(nD@^nY?5#+@^t_@A7iDE$G*35W)DmY?bbA9Y!+vz!ibERX#bXg9LKPJU%Q;X2) zVePpdij-(qSMQzcQQN+Ln_6tg_Pm_;xDhY_Q3FWgS|jkm(q`xm97i^4K-jVj78@UGdE-Vq=Pat&LD>fsMT(?Mr|^9V);7*t`JfEgBI1_-wxfP~l^t|s96 z2rez*Vj0#MVC4iBonRRaRv=+b6_%s_7X|kEB_9M?U5&V!;)@^(lZ(-q8PVo$iweSN z7oTx2aNu;zw?We%`ebyNw=67|#~D_STM?Wr2zgrS1e8W5*8f9O{^0DxrQV1Nr5 zFsdw$l}bstNYuu8MD$6(YRpMS`=w7tPLh<>I^&^2M#+go6^3e7EQ(%ZNk++rNO?6~ zPmkSTUbyL=xAzgu7hFw$&OLiH>kot?5#bS`LkXC?H#lRj!gg#p3fyp8=o+^IZp`2; z12{k=@?hf(jk!ZdW5rm`WM%QRn_MZxeN_~=I)|8Rw5WklBntJ4QUULx(wk@AqpFVH z0jB-cOV;$!w@V6KPsHvfHFg^EQv zUQzNpQ zNEil%bOv*lD4oHaC14K-Fa+!Y0fqn+A;9==00uz7F2L>HIf?Sk?G-wW<7|lQg(>is zlV`Vy2}BG&+$ryOw`2>s)9TlP*~BA|2b9UI_7Z#BUU%ZH=2l^7@>an5z5m)i?Ue5b zTjmuZcY$FxGtyeI63SZ2jGT%?%YJ|D?x$%vA{C25*YACQ|H#Vr195ZdyzYm=Ft1qs zDAA3R6qQb+L>cDj&aMgR9Nq1;2zc!*di~xV7h9J0*OpH7@Fr+sX!ISRxyW`(hUsQ8 z(jl2YJ-kcv(f22KMVRrN^?Mhn(`PSI%e`lK6EIyYb`5AQGAdZonNX>6UN+_5e8f1P zb(dGfY8~64cUt!Yb^6@-K;8t_N>q9nXf84;pm1ckA2k&`ph)}9HZ@ekE5dSZ8}#lp zbsD-SwuSK~;G{TklWT}ZFe*3;CN?nx5oYx*<7b17ukwl@_y-&GE|Ha3Og|;Y2`2Cd z!vM{Vf>FWQ4&a$)J#8rJ+kNHGySySO@nD1AOY+5X*@dc7!Rw)eLkAc6m3KNo2N$NF zH{Qbt&gV)#-opqkWFtP_!w4?q7cZcJ5JiLxynyCkLzD`ezB}yd;LKUqUk{QJ4#qd_ HV=Dgv;q`6* literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterXCenterY@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..8093c8976182b151e956da8fe503b78d47c45047 GIT binary patch literal 12274 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXW!1#cJ4JeZJl%)koF%}28J29*~C-V}>;Vkfo zEM{O(v;bj7*J*|cKtY*I7yl3-%>=|C@BxT5pfr#zzH(ySzR5tk)YHW=q=NCyEyuhV zO97XI`~ELY-I;gcMDJe-i8Fa05|=qm)v2^315ReKgbVW<-W@&PEstzaM1$@Du>_bhum-r=t>?2x z)dIt8G#nslXEYo}!vR=QjFu0Of^{?;M#BMX9gC}+)Z3>CbuXAjhU{m#nivA|4xj{9{(V0+Hs?B34DA(YEncOo3eNaiB@Cz*tiYxe`_6?gjSqoeSWbNXv~39X$}QYQ^b@xOq|mr!5_&uUYK!ECnC9l4eEx5 ztFBB!H-wRlV}=>``|5phpeqn=*xm?r!>E@=V`((u!SnNIUIG_Uqs7B$AwF6!jaIdg z#>i;vVYH1m+H8e2>_=PTkiN=j=VG)Ai0Dz))cpgF!~g%!_F*w_L|^ zLmn5$`~NTX9cIord^cHf#?zXHw@p!3p8x(?^LH|+TYbUk!_Qxn8(6k+T8KwKUv&TS z`;)+aH(OnO(*#Jbd&AxzzyAwOXei!TrZ=lSlTG4>`G?=aEj*wu`z*%O-N0_W|AI2T z8IX?mWkmj5|8JiHBj=TKte;<40f*NTcJZe0 z`bKVi%Jui8&Ahl;7s;r6;EF=-4XiVeLtMfl@c>>EsHV9^GSyE(wPWBbceqM!JFy1~yA?Qr!En(!$9!o5B+?Mv2a zgi!}>HvrAfVFWF&K*XINFz%Y`n08Iy@ZEbx&6C4$^#upxgAZnS((gOfH-ClH wA?dj>o>1Q2&EJk6_z8U5)h)qWES74dBvQ=9aUd`U?1w6*s_R! zsO?2x)+3ZvKpM6GQXYqIZuz>OKXX@mP?Q! zYx@o|1Ty3uK_{3Nb_eVZBFn%Gzzo0)z>3s7C;}(~D1z)ajbDkqO~HK#FBWOhHo>e(H|N z9{Wq$ki*fQt6tAm-%bOY7SLuPpK#d8*%QD<8&US9kO!EmG+P8w&L)W1vb@87Jf$a?2AaG1&=y+ zTim_y84ee3pUQL)dFV;MumgjT)IIYXCSLR&$KeZ45uHpxckrQKoyUySdk|6*5S~0> zO2obVtwz_`kuxN`F~f{_8F^xA6c7gZI0W->yF>kKn-aMyf!xy-{ocitC?Vu0AQb8F zs)gP|j^fCL$kZPOJgO1`8;3wGR>^CrK%SC7KGYlxCE-yO5S}I=z~aH;!OoM(19qN* zJpOyybh|g=8f`3Q`sL+GgwpPl;_ULQrS+as!&qXmsn&aj@!m3{e~2#FZolqs>YVM; z5LLLuyWbX@X&ddYvKfMVBAuA=cdazFT%a)bmzw%3!x1Nw%^7C=*Uq+AJ9sDW)t-}| z?4a6o>LSkCJ)j%^NmiJpxKh-x{@`Z>H(%9BV#Go(Eh&R$#PG58|ex|0;wR@N- z)==#`{_U>joWaj6^rjojNxs*zxD3ZdqJy%+AEm+6$%h_D268xpWPoG%ke71%qab<_U4dU3Yw RIexK$iuWHZ?Ayn+{0(j_dR71c literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_CenterY@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..7e824c2073a478b89c80778318cf09300a374f80 GIT binary patch literal 12114 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXW!1#cJ4JeZJl%)koF%}28J29*~C-V}>;Vkfo zEM{O(v;bj7*J*|cKtY*I7yl3-%>=|C@BxT5pfr#zZklmC@ehz@^mK6ysbG9_>-5?f zO97XI-|oNszESbu(U)8tQJ)q~?MO5{`SVYWo&CR44UB9O2?*lBZQh!Ff8}4?e{P?I zBGJ%T&&;(!yx>1O12d1o0XR``sDX*+7w3z-KX-l@B7_e#Fs)(GK7g$9zyVHXE`t~9 z3wFOPzI_~S08kIt2B2bNB+U%W>O2|;7R+yWx9xncJhFWn2Y`ytAZzBCW3YgsxsGYq zZM*HY$o4G&+RtTxtl0qW*xz@)jXHKTWJi<1Xa)loU8BihG#L!WWbh&U|DTBsEGic~ z4qPzWf~cm&fHfvJ1E+_=-MrKDC!K+S<12Y%bKW;s@e#(KX`unVXx{Yp3ZPOMWh1aedBmx>l6GrVB4Y$#BFq(gO z3`WZXiGReiG+Hl=mIt6t!)Q7fO$UP`9sB_fkpKUmb#sO< za4`H3XfXWkEyq3;a{=Z9|NbwpPXETt)OLc|n7PfLSyXCG!BUN%pMU;xX99MQ-I?y6 zfBrgN5;4U6=jws;WtZ%QCN!{E3H^B8e$L8y61-de;Ccho8wLj^p%tD79vJOF^wEWw zxeCr++yA%?)E7@-I(htD)M11+2ga}lRfWb`sC{$=U?2T@&R_NSS8bx1WI0vjt+RQ( z5u@;|5scFsSh9o+w(#h}MH`PaFvV>C`hLg4&)TX6GaWu{MC;sxI`9kB7@bdFNP>6y zJ%FR@rqTaTR-2xm2J|s0jD67lJKbmkp$KVa44XRR6s(Z{!`r9 z&8inBlH|BPX{-G~Op{3ph538JKj(0ksF32g(I^5X)X^v!%|#8+vSc(DflA@gf^M`f zl7Lo}qjk|}bAfHNMF(o^jkf53ZT!(jH>m$H+FSs21EHO^(dGi4=0fBA*!2P_@L6i; zsNsE3*ZIL~`!9e0FRVU(&k$*;*8l(O-31)rol8i!`bPc%-VM?T=Ga%2S)aXKKkK<{ z^>M@+Fnv5L%l=t$ekxzyicrk+#vIRzF~85>=0B@McI1K6c$T>B_iOrdH&YSGN56UT zEM2?2=kM~$dmGCU3)?;z;c?9R$qM!5Q77Q@^al>q;qmD^ll=dg^UB}IB39V#0rvlK zt)u((TIIz3xnhVl1qK^{5ruOx-)OMoTDmwI>^K%`j;3}T%Th-R85}EyM+=z|UC8`r YIOu*z{y@s-y`VMkp00i_>zopr0KzicBLDyZ literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndX@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9064f337045037ba70a7fc6fdc89d968e4c12dba GIT binary patch literal 11609 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXW!1#cJ4JeZJl%)koF%}28J29*~C-V}>;Vkfo zEM{O(v;bj7*J*|cKtY*I7yl3-%>=|C@BxT5pfr#zw!8Yyzga-K-P6S}q=NCyO-C+g zMFG}>JO3}`{m++wae`8c{@hb5D&D{QnRS4Hna2P^INUy9E6*w-A(HX`B5%@uez*h! zb93^3?k!AQHLN@#yb=iwjBF5Mg>(UHm_wr{| z1TM(P_KGd0K{OyC=)i{6{MN6Nr@+ifNGSU9{nzparnXMrR=8k8<1R+=1Xht5A_i|> z9X|IRp!1{8u(J`{z18bEQXF_b+uuP6;(VKI7(Ym{ zurVSF>qrDNh$gVkfRAZ`44GlDfT7uru{Lq`Q}bK5j1UV;3Z!wXzN_};Udu^$J4Cg} z%_fonT*Z+FA3Oz5!SiSwSm5sveq3C-l15HuE`t}s7sR(k)-7JEi{#DicvR;edgnW%=E-4rPhmrX1JGpA1f*^VaDMPm z0~60LmM^Wj)9zXO+Ts)g0$e?nK~05y#U_h@h87)j8mFsBBWaza_Y8cM=ACjL}c8M1_uFT-+>gTe~ HDWM4f^iQs7 literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndXCenterY@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e634d8e09018ec9def9253f3d9d92d4070af6f82 GIT binary patch literal 11738 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXW!1#cJ4JeZJl%)koF%}28J29*~C-V}>;Vkfo zEM{O(v;bj7*J*|cKtY*I7yl3-%>=|C@BxT5pfr#zF1~lUf&>GD*kex@$B+ufH@8mb zrWgt^A2?~BS)Y_4w<7W2jn~aSSGKvFoIn5k^W*lC1_v0Jc@RX+?#A=m|2Ngy|L>Gc zXkcWMfD`|CH4elb_{)l-+`u5wfsw72X_vIUc^#551`8Op4{#YEn+#Wa+irX9sM668 z7!85Z5Eu=C(GVC70X!jafSK(d-@7^$g~n>8JjOdLhKTC=LY~9lsSPYjoDyYk51;!b zkqWP_J7oU8U!T5Dq4Bz~flrPGlJQ%l0~%Bn8ajb^NhCHm`96((V}3qC>SjYfSq4RQSe~i0e#@8_y7M~>rU_ihr2UC z!`*Lh8WtTk5MXxr`~UK$zm2N{5_;zPRB_#u;rvuN|M^#KW_Zt)k!=^R&3buYuXcjN zcl%wAZ{8n9>byRMotMeGZCa%xCNY35Srki!gzRH?TtwJ#fXuLiHrANx99XAWsG3& zKhM!9z*|p@MgeHN314GkG#7xfIKCF=Xh8%jW$_MHjuu3~a(uKP8m*2;tK-pJFq#WS zbHUKh1)z50|NIBl6TkeIe<}U97cp-N?kBrOb|et&OC6S2{;GJ7}kKIw2_mU z%ix9Z1@XMiHH)L8k-9>8z|JDm8U|x{7Yk&TFmUSig8G8poV)W6JyDj2D{o}Qqgww? z+E2Ib+Yo&zo;yHefNn`cbRB^#H6D!v3;Z3*h0E@@oOHKCDCfqbI)2C5PrZ4$NPgM@ z?7{(Eeg@w61bM(mBA~%>e#1MpH~Tmz_tzl0lwv?jajD){F#DALmZ;6%@LSEt z?h~S~9f4;-+pj~p)ACEQPa{I$K&}HY;zbkS@dffg!l4Exo?k2_=84~ZXVg4F3^;5^ zz^(dA>usI;FRo2Pgh1o%24FJLK7fcXAnQO2Bdf$0&KG%y-pw}nd7>SzJmDZ7)!f@7 z_g!483m=wXVBU@=$=+?bYo7RNB0pUDXaXNi;E0?(n!ra(@L^wqA7H=w?T~!tPTQZr P!Dj|fS3j3^P6;Vkfo zEM{O(v;bj7*J*|cKtY*I7yl3-%>=|C@BxT5pfr#znte^Fz86TFd%8G=R4~4|b$YFg zClABHkN01G-`HrgL5=xP9J{L0wq29godfkR3=S|bLkWcj)(s44C}u-dZmZo^JE{^A zRHLCZnnIwtY&3Vg7%dq_%TG`RGFmE*mWrdLBCKUKS}Kl~ile3CXm0|kmp0l~B-B={ zIB?*8_ilT&1g6iNH<)g)7$VA$g1ZfWOc{kEW;UFD_bc^zO^*>=Zq1%w=l9S1$tqk? z%cLjFFM<^0>!bw^08P^tF1Q)H|IM8KK)77v)bRh;_usYL&v{1ufzd~YL`3ENU>74} z4wKpjBgW}>zn(t#Qz8{E7g70je!cByZnY1;SYp)VG?044G3E*lz6V&s)E;cwU3b$` zelbE$d;P!l^}CDfglG64NPX0J2;SX3aG;bK=uMx5R3^RMucy!bYv2y-i9 zFre`@8_NdH83#_W#FSmvw|<^H1umEH^lJUTuV?gU9C$5UFtcI-(trRk*cr@l2K)Yh zZ_oYoO|WB}-pS4d8$$rb%XMCk1Yv{5shm6Btcx!@cU%K5ci`0O|9@W3j5lb!PFk?n z|Gho;^XvmPq{Z;h>&E*NnAVdP?Eimlw|;Kk_>;64{=7cD?jTDXX~F*gYkv8;d|+xP zDTbfNpa0XuxrfAHe|`Vmock=#NQ>doj0MdlqZtdO=o~y5tA=Sma3uBrfAbd~r!p{z zwSb0D-`;fOa#j>zJ-GA#Qr`c3`4=ZBrRdK+wW8wvyPsL`dc)v=!|emM@~k2fA{qZL z@+R%)hZj=}%+1OBxwkNJ)v)q}@FEqxE2Ilp!yFnt84s_sd$R_)J~?^uKKBA9E(tX= zaH*``3oe$Q6N_d;R))E{%j4H;`)%pz|4VI{)?j zQRk0_KBy@%ns|VLFq(BC<=$w?18f3}7MzeSM#5;dC6O>%>q0ujjled~Xrp1Y$p+~e zj5ZoZ8x4?h0a#y-HX2474bYJjiGzW1 dc+OgP_1hu&^ZSk70|%xVJYD@<);T3K0RXgVDjWa+ literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithOptions_EndY@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9f88fcc02d9e2d74fdeb23bcf8b4e834fb0183e1 GIT binary patch literal 11827 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXW!1#cJ4JeZJl%)koF%}28J29*~C-V}>;Vkfo zEM{O(v;bj7*J*|cKtY*I7yl3-%>=|C@BxT5pfr#zTBz;N_8myudb&7Fa7=RNrR_P@h_yZ?g5 zC=xt>qy-M_Yxr%?Ad%3($Oa=CTNoL6s+e~5&hM2+GKPmEfl;SH5}V50`MIMip+Pko zN~0+Rl9^!nXf$^q%A?WTF`7F@bH`}Oz_e?$R2(f8L9Nlz8ez0X7_AXTYXr<1;m_0g z_G$@CY8wn0+ow5q!#k)Ck{lSK8D=zap7D9`X+!ba!;|1r4h-k_{K~IcSUg?rLlG07 zZo3$~1AE|T1B2KBpn1l^J8a&>yxx!k@A3*9Fkb&pfB)*AxxzPQG&rALcog1!E-+MR zVBNqujfJ$~!!Ryv6OFUyBBtN$Mpw<^zM(pP_Qp(w);-notem1;RmY6ixQrw^ao z%I6L5TQf4+e17%+)9zXK%^G_-Ew;!+A^Pd!%uEpsvq!xJin7tz8cl?wc?(jmjTRY{ z7Vtk<85sWmFK20zXJC*>2MwgYz3JG+Y$(8T@aO;K$G?fkd{I<1sJ^1m<*+o)_WWyg zMoy0f6A%2hS#K|iXr=$jeo#I4%j5eba{p%IC+CAk!D%0u2^tGE16`TQs zbc3owqYsmpcP65i(qZE{Fmv_)r@Lq6djox2bR%*#QuW4jfGq*+ol8MTb=0VL4vfam zXkz0T%{$DaMZsuMU@%$~j1~n9qea1i(V}3qC}18f3Py_p;Lz1*2Vt~TFj^Fh76qe4 t0dORLv?v%Y3Py{9(V}3a6$L*S4!R$bf7`He7jP{%gQu&X%Q~loCIFuRMyvn; literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c2bf62b0786395a78fa42272875d4077801e4be0 GIT binary patch literal 12389 zcmeHNdrTBZ7@yr+F7EY+&G}kHjM$6N|k~Hl|8jI zzUV26N<2B57@DAP6?_#cTidEg;V#9Zwknra0ShV-q=LeB7I6341d{us=|6MHKJMmb z_ct@&eDnLh-|UOgN4UzS$^Zbak-G3$03<8`n0=fS{*wBb*b6V%w%CXTpuBO~6ZnNp z*TroEz+*0dV2cX^Y{M0)|l8 zboQ1#jX|DuZ;RAj0W|0x4@jM|fcZT(VYmWc>i{N6Z~e1ndr4&!nc!uamf8G4l$4?w zZ0IDhU@LLOO_jOo=;V>Ns9AO`3r=0>UwyT#?;cX8XaV8Xzn0afE+bichE8lNe*%a$Q2-0fLsA` z1t@$&(I^V8QLX@Ga!_Il<=If$5JO2_l%u9zLG6L#dUsc80hsbJFFHK^H7wuVUruN_ zuBmbJd?oE3GCOYER?T{9-m#MSTW#GhBXEUL8-cf2+d3-bf<_=6OtmKmKly@CDGm9= zVE>nujYcuG|0>W7f`O4CQWfgU@(%@R!SDlvCse+Cez~N3rl*-&%*7K^$^f zi9w+Uj%$_N@89EET(hG4fuddKOn2wra5b%d-%#h&xJExL>)T9bsGRBU+CTCdD~_`3 zof=o~=fCW@LF$(wMbBbr8N zHDX3s23n2y#UHIkh}21`5f1%-lRDO#xNMw8*v&J{i0^LEVws@p<@lXkXomG}zy^W& z({_$qMqq@%GlyumVL*BxRAZTvpM;G@Jh4BeHiJF8@Nb%G21>I}f1NCrc4>G@EmI3I z179!w8iULG$@Yo@WpS{d7=zGsEe1L6az@;A1YsH`NGH;^Xl+)}C2qDgp!c9yng~l^ z1qaj+zaFynuuyE^Q^ligrJsp3UEVoo3@rNY-s0Bm>!HN-@W5ml~IUA7}ztTV|kGBaIh)lAN{-CjgPooPKZx{cXQWhG3fx&b0^YhHBA;{ zCbgwy3@pJ7zvULSx#e6Jk;Vkfo zEM{O(v;bj7*J*|cKtY*I7yl3-%>=|Cumy-Upfr#z)O*3xVIPo=^mK6ysbG9_)3J-$ zP=Mv&&;QGhe-n@SqNr$4eMO#0c) z^*~Jr7;+e2PYY&-{MuKxdY_pE$xpl^$AM6TA6gt-hT$Cd#0&ZVHJQSXe#&S+vA%{!w- z!DvA}TJMZjqrgVaXi+d)6pR)Hqea1JQ83zl8|{sbcCJVGSV1E{aIES7|2<_KM}dP( zVW2^#x1&Q$JflNQqXU4WSjYMvH>cqF}Tr08K=X sb`VB82%{Z@(N@7|Q9y99P$PeNmf-xq`y!76hng5XUHx3vIVCg!0Kh?)9{>OV literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidth@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6796058c28ee05319977f7227dde29bfc0889eb1 GIT binary patch literal 3946 zcmeAS@N?(olHy`uVBq!ia0y~yVAKKP2OMlb5&7_dT|kPlILO_JVcj{Imp~3@fk$L9 z1B0Rk2s64)GfV&q%4E9uhX83NAO?XCK&%0!fo!%*tOcg#Kzg31i(^OydsSvUlCNa`@LaHOypx$fm=WMbhF0}@6|GZ*b^ zWng4t>jn~%jXr_*of;Szn_U|i8had;t;kn&XlOX7-2 zsi_sHB!we|ZEG~p6vl&2KqW>jM$NZ$ITRWi5)^?-B$;M9z7+$S>97H05l`cp1!deo zGZi9$N*vl8(gJp~0?ibFTYGoZ+R;EBDM{-6yPwGi82LnGe(>w?w=%L@TqtnZz;e6w zY-0~|lk2sfHR~1}6Wvmyeo#o~;3J1)j0csn-L5@-_U-MluWAuZsu40LEEFPA&YpP1 zl@;T7x*VuVNQW;)MnGp%T1sqQ-u=9oe4whPqoc-+hTUjF8O;?Wmngp&{4DP*oDf>I R8`z>?@O1TaS?83{1ORmP_*nn| literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASRelativeLayoutSpecSnapshotTests/testWithSizingOptions_SizingMinimumWidthSizingMinimumHeight@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..d4a15f979b1619e2d08dcc2c4c3280395ee0f062 GIT binary patch literal 2696 zcmeAS@N?(olHy`uVBq!ia0y~yVAKKP3=TG+$lEQ)_<wAm z5MVO=GyC3U!<}r*OZad2aR?{~2zFdCSJ66I(ZskZkk`yzkV&z}W2Cz|h#^uxv%XqC-Q&K_!O<2RDV_E87JW91bK1DmW-8 zPtbf74KzGq1CW>?ctYf>F3<*p2q1BSBc-Sx z+W-s1|NqaQa9sy134eo1!ndR4Af%KZO>I<8ZKGA@XoFyIHg|yK;60J0mE3*|TY=>v NgQu&X%Q~loCIFws(cu69 literal 0 HcmV?d00001 From 4eb3356615f4ba76ef4c17c55c90de87a17482b6 Mon Sep 17 00:00:00 2001 From: Rahul Malik Date: Sun, 20 Mar 2016 18:14:58 -0700 Subject: [PATCH 18/27] Basic a11y support for ASButtonNode --- AsyncDisplayKit/ASButtonNode.mm | 12 ++++++++++-- AsyncDisplayKit/ASControlNode.mm | 8 ++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index c021c357..2990fe2e 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -60,6 +60,7 @@ _contentHorizontalAlignment = ASAlignmentMiddle; _contentVerticalAlignment = ASAlignmentCenter; _contentEdgeInsets = UIEdgeInsetsZero; + self.accessibilityTraits = UIAccessibilityTraitButton; } return self; } @@ -102,6 +103,11 @@ - (void)setEnabled:(BOOL)enabled { [super setEnabled:enabled]; + if (enabled) { + self.accessibilityTraits = UIAccessibilityTraitButton; + } else { + self.accessibilityTraits = UIAccessibilityTraitButton | UIAccessibilityTraitNotEnabled; + } [self updateButtonContent]; } @@ -135,7 +141,7 @@ - (void)updateImage { ASDN::MutexLocker l(_propertyLock); - + UIImage *newImage; if (self.enabled == NO && _disabledImage) { newImage = _disabledImage; @@ -170,9 +176,10 @@ } else { newTitle = _normalAttributedTitle; } - + if ((_titleNode != nil || newTitle.length > 0) && newTitle != self.titleNode.attributedString) { _titleNode.attributedString = newTitle; + self.accessibilityLabel = _titleNode.accessibilityLabel; [self setNeedsLayout]; } } @@ -331,6 +338,7 @@ default: break; } + [self updateTitle]; } diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index 54ac169f..41b41b09 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -77,6 +77,7 @@ static BOOL _enableHitTestDebug = NO; } #pragma mark - Lifecycle + - (id)init { if (!(self = [super init])) @@ -89,6 +90,13 @@ static BOOL _enableHitTestDebug = NO; return self; } +- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled +{ + [super setUserInteractionEnabled:userInteractionEnabled]; + self.isAccessibilityElement = userInteractionEnabled; +} + + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-missing-super-calls" From 9e76d7b603f0b82612344ca96e6e051ebcf75836 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Sun, 20 Mar 2016 23:14:53 -0700 Subject: [PATCH 19/27] Add method to cancel layout transitions in progress - Also make sure a transition isn't invalidated right after it passed the validation test and before it proceeds --- AsyncDisplayKit/ASDisplayNode+Beta.h | 5 +++++ AsyncDisplayKit/ASDisplayNode.mm | 28 ++++++++++++++++++---------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index 11cd6d0e..4bb108b6 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -98,4 +98,9 @@ ASDISPLAYNODE_EXTERN_C_END */ - (BOOL)placeholderShouldPersist; +/** + * @abstract Cancels all performing layout transitions. Can be called on any thread. + */ +- (void)cancelLayoutTransitionsInProgress; + @end diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 0596eaf5..f1b6aa26 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -593,14 +593,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _layout; } - if ([self _hasTransitionsInProgress]) { - // Invalidate transition sentinel to cancel transitions in progress - [self _invalidateTransitionSentinel]; - // Tell subnodes to exit layout pending state and clear related properties - ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) { - node.hierarchyState &= (~ASHierarchyStateLayoutPending); - }); - } + [self cancelLayoutTransitionsInProgress]; ASLayout *previousLayout = _layout; ASSizeRange previousConstrainedSize = _constrainedSize; @@ -698,12 +691,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } ASPerformBlockOnMainThread(^{ + // Grab _propertyLock here to make sure this transition isn't invalidated + // right after it passed the validation test and before it proceeds + ASDN::MutexLocker l(_propertyLock); + if ([self _shouldAbortTransitionWithID:transitionID]) { return; } - ASDN::MutexLocker l(_propertyLock); - ASLayout *previousLayout = _layout; ASSizeRange previousConstrainedSize = _constrainedSize; [self applyLayout:newLayout constrainedSize:constrainedSize layoutContext:nil]; @@ -769,6 +764,19 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // subclass override } +- (void)cancelLayoutTransitionsInProgress +{ + ASDN::MutexLocker l(_propertyLock); + if ([self _hasTransitionsInProgress]) { + // Invalidate transition sentinel to cancel transitions in progress + [self _invalidateTransitionSentinel]; + // Tell subnodes to exit layout pending state and clear related properties + ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) { + node.hierarchyState &= (~ASHierarchyStateLayoutPending); + }); + } +} + #pragma mark - Layout Transition - (BOOL)usesImplicitHierarchyManagement From b3e98e9f5fc3a99ee674d54b1920317b5f42a3d6 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 21 Mar 2016 16:58:39 -0700 Subject: [PATCH 20/27] If the queue is not fully drained yet force another run loop to process next batch of items --- AsyncDisplayKit/ASRunLoopQueue.mm | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/AsyncDisplayKit/ASRunLoopQueue.mm b/AsyncDisplayKit/ASRunLoopQueue.mm index 8519a61f..a18f7a8a 100644 --- a/AsyncDisplayKit/ASRunLoopQueue.mm +++ b/AsyncDisplayKit/ASRunLoopQueue.mm @@ -12,8 +12,13 @@ #import #import +#define ASRunLoopQueueLoggingEnabled 0 + static void runLoopSourceCallback(void *info) { // No-op +#if ASRunLoopQueueLoggingEnabled + NSLog(@"<%@> - Called runLoopSourceCallback", info); +#endif } @interface ASRunLoopQueue () { @@ -22,6 +27,10 @@ static void runLoopSourceCallback(void *info) { CFRunLoopSourceRef _runLoopSource; std::deque _internalQueue; ASDN::RecursiveMutex _internalQueueLock; + +#if ASRunLoopQueueLoggingEnabled + NSTimer *_runloopQueueLoggingTimer; +#endif } @property (nonatomic, copy) void (^queueConsumer)(id dequeuedItem, BOOL isQueueDrained); @@ -47,9 +56,17 @@ static void runLoopSourceCallback(void *info) { // the queue to stop. Attaching a custom loop source to the run loop and signal it if new work needs to be done CFRunLoopSourceContext *runLoopSourceContext = (CFRunLoopSourceContext *)calloc(1, sizeof(CFRunLoopSourceContext)); runLoopSourceContext->perform = runLoopSourceCallback; +#if ASRunLoopQueueLoggingEnabled + runLoopSourceContext->info = (__bridge void *)self; +#endif _runLoopSource = CFRunLoopSourceCreate(NULL, 0, runLoopSourceContext); CFRunLoopAddSource(runloop, _runLoopSource, kCFRunLoopCommonModes); free(runLoopSourceContext); + +#if ASRunLoopQueueLoggingEnabled + _runloopQueueLoggingTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(checkRunLoop) userInfo:nil repeats:YES]; + [[NSRunLoop mainRunLoop] addTimer:_runloopQueueLoggingTimer forMode:NSRunLoopCommonModes]; +#endif } return self; } @@ -69,6 +86,13 @@ static void runLoopSourceCallback(void *info) { _runLoopObserver = nil; } +#if ASRunLoopQueueLoggingEnabled +- (void)checkRunLoop +{ + NSLog(@"<%@> - Jobs: %ld", self, _internalQueue.size()); +} +#endif + - (void)processQueue { std::deque itemsToProcess = std::deque(); @@ -102,6 +126,12 @@ static void runLoopSourceCallback(void *info) { self.queueConsumer(itemsToProcess[i], isQueueDrained); } } + + // If the queue is not fully drained yet force another run loop to process next batch of items + if (!isQueueDrained) { + CFRunLoopSourceSignal(_runLoopSource); + CFRunLoopWakeUp(_runLoop); + } } - (void)enqueue:(id)object From a937184a0be7feb066b0a5d21d7b11dd00c26ce4 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 21 Mar 2016 17:10:29 -0700 Subject: [PATCH 21/27] Remove CFRunLoopWakeUp in CFRunLoop observer callback We don't nee CFRunLoopWakeUp as at this time are currently running in an observer callback, and after running all of the BeforeWaiting observers it must surely check for more sources / etc to run before determining if it will sleep --- AsyncDisplayKit/ASRunLoopQueue.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/AsyncDisplayKit/ASRunLoopQueue.mm b/AsyncDisplayKit/ASRunLoopQueue.mm index a18f7a8a..e879bfe7 100644 --- a/AsyncDisplayKit/ASRunLoopQueue.mm +++ b/AsyncDisplayKit/ASRunLoopQueue.mm @@ -130,7 +130,6 @@ static void runLoopSourceCallback(void *info) { // If the queue is not fully drained yet force another run loop to process next batch of items if (!isQueueDrained) { CFRunLoopSourceSignal(_runLoopSource); - CFRunLoopWakeUp(_runLoop); } } From 3668f45286a56a87d200aea0299caee9d2e4a880 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 22 Mar 2016 08:09:59 -0700 Subject: [PATCH 22/27] Prevent deallocation of asyncDataSource and asyncDelegate in ASCollectionView and ASTableView Grab a strong reference for asyncDataSource and asyncDelegate in ASCollectionView and ASTableView before executing the range update to be sure they are not going away while executing the range update. This can happen in range updates while going back in the view controller hierarchy --- AsyncDisplayKit/ASCollectionView.mm | 9 +++++++- AsyncDisplayKit/ASTableView.mm | 9 +++++++- AsyncDisplayKit/Details/ASRangeController.mm | 23 ++++++++++++++++++- ...SRangeControllerUpdateRangeProtocol+Beta.h | 11 ++++++++- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index ae1f5792..a91c84fe 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -1139,7 +1139,14 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass if (![node supportsRangeManagedInterfaceState]) { - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + // Grab a strong reference for data source and delegate to be sure they are not going away while executing + // the range update. This can happen in range updates while going back in the view controller hierarchy + __block id asyncDataSource = _asyncDataSource; + __block id asyncDelegate = _asyncDelegate; + [_rangeController scheduleRangeUpdateCompletion:^{ + asyncDataSource = nil; + asyncDelegate = nil; + }]; } } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index d8d9037d..9e5ad025 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -1072,7 +1072,14 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass if (![node supportsRangeManagedInterfaceState]) { - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + // Grab a strong reference for data source and delegate to be sure they are not going away while executing + // the range update. This can happen in range updates while going back in the view controller hierarchy + __block id asyncDataSource = _asyncDataSource; + __block id asyncDelegate = _asyncDelegate; + [_rangeController scheduleRangeUpdateCompletion:^{ + asyncDataSource = nil; + asyncDelegate = nil; + }]; } } diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 7935564a..c9fffb4c 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -27,6 +27,7 @@ BOOL _didUpdateCurrentRange; BOOL _didRegisterForNotifications; CFAbsoluteTime _pendingDisplayNodesTimestamp; + NSMutableArray *_scheduledRangeUpdateCompletionBlocks; } @end @@ -46,6 +47,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; _rangeIsValid = YES; _currentRangeMode = ASLayoutRangeModeInvalid; _didUpdateCurrentRange = NO; + _scheduledRangeUpdateCompletionBlocks = [NSMutableArray array]; [[[self class] allRangeControllersWeakSet] addObject:self]; @@ -111,6 +113,15 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; - (void)scheduleRangeUpdate { + [self scheduleRangeUpdateCompletion:nil]; +} + +- (void)scheduleRangeUpdateCompletion:(void (^)(void))completion +{ + if (completion) { + [_scheduledRangeUpdateCompletionBlocks addObject:completion]; + } + if (_queuedRangeUpdate) { return; } @@ -118,8 +129,13 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; // coalesce these events -- handling them multiple times per runloop is noisy and expensive _queuedRangeUpdate = YES; + __block id dataSource = _dataSource; + __block id delegate = _delegate; dispatch_async(dispatch_get_main_queue(), ^{ - [self performRangeUpdate]; + [self _updateVisibleNodeIndexPaths]; + + dataSource = nil; + delegate = nil; }); } @@ -320,6 +336,11 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; _rangeIsValid = YES; _queuedRangeUpdate = NO; + for (void (^completionBlock)(void) in _scheduledRangeUpdateCompletionBlocks) { + completionBlock(); + } + [_scheduledRangeUpdateCompletionBlocks removeAllObjects]; + #if ASRangeControllerLoggingEnabled // NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; // BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; diff --git a/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h b/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h index 4f34cd1c..f9fcbf77 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h +++ b/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h @@ -49,6 +49,12 @@ */ - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; +/** + * Schedule a range update and call the completion block if finished. This drives updating the working + * ranges, and triggering their actions. + */ +- (void)scheduleRangeUpdateCompletion:(void (^)(void))completion; + @end @@ -64,7 +70,10 @@ @interface ASViewController (ASRangeControllerUpdateRangeProtocol) -/// Automatically adjust range mode based on view events if the containing node confirms to the ASRangeControllerUpdateRangeProtocol +/** + * Automatically adjust range mode based on view events if the containing node confirms to the + * ASRangeControllerUpdateRangeProtocol + */ @property (nonatomic, assign) BOOL automaticallyAdjustRangeModeBasedOnViewEvents; @end From 41362fca3942b71cb092376e5baaa78652d1e19b Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 22 Mar 2016 12:43:09 -0700 Subject: [PATCH 23/27] Move call of range update completion blocks to consider early returns in _updateVisibleNodeIndexPaths --- AsyncDisplayKit/Details/ASRangeController.mm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index c9fffb4c..10ea6c2a 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -134,6 +134,11 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; dispatch_async(dispatch_get_main_queue(), ^{ [self _updateVisibleNodeIndexPaths]; + for (void (^completionBlock)(void) in _scheduledRangeUpdateCompletionBlocks) { + completionBlock(); + } + [_scheduledRangeUpdateCompletionBlocks removeAllObjects]; + dataSource = nil; delegate = nil; }); @@ -336,11 +341,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; _rangeIsValid = YES; _queuedRangeUpdate = NO; - for (void (^completionBlock)(void) in _scheduledRangeUpdateCompletionBlocks) { - completionBlock(); - } - [_scheduledRangeUpdateCompletionBlocks removeAllObjects]; - #if ASRangeControllerLoggingEnabled // NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; // BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; From 76b38cbe6e6bbd7aedf8550e717631236f43379a Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 22 Mar 2016 14:32:32 -0700 Subject: [PATCH 24/27] [ASInterfaceState] Clear the "Visible" bit immediately upon deallocation, rather than waiting for ASRangeController on the next runloop. --- AsyncDisplayKit/ASDisplayNode.mm | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index f1b6aa26..fa696f58 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -337,6 +337,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)dealloc { ASDisplayNodeAssertMainThread(); + + self.interfaceState &= ~ASInterfaceStateVisible; self.asyncLayer.asyncDelegate = nil; _view.asyncdisplaykit_node = nil; @@ -2076,13 +2078,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [self setDisplaySuspended:NO]; } else { [self setDisplaySuspended:YES]; - //schedule clear contents on next runloop - dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(_propertyLock); - if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { - [self clearContents]; - } - }); + [self clearContents]; } } else { // NOTE: This case isn't currently supported as setInterfaceState: isn't exposed externally, and all @@ -2094,13 +2090,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; } else { [[self asyncLayer] cancelAsyncDisplay]; - //schedule clear contents on next runloop - dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(_propertyLock); - if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { - [self clearContents]; - } - }); + [self clearContents]; } } } From 953c0f51f05738dba96969c0c6774c18dc815b12 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 22 Mar 2016 14:37:49 -0700 Subject: [PATCH 25/27] Revert "Move call of range update completion blocks to consider early returns in _updateVisibleNodeIndexPaths" This reverts commit 41362fca3942b71cb092376e5baaa78652d1e19b. --- AsyncDisplayKit/Details/ASRangeController.mm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 10ea6c2a..c9fffb4c 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -134,11 +134,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; dispatch_async(dispatch_get_main_queue(), ^{ [self _updateVisibleNodeIndexPaths]; - for (void (^completionBlock)(void) in _scheduledRangeUpdateCompletionBlocks) { - completionBlock(); - } - [_scheduledRangeUpdateCompletionBlocks removeAllObjects]; - dataSource = nil; delegate = nil; }); @@ -341,6 +336,11 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; _rangeIsValid = YES; _queuedRangeUpdate = NO; + for (void (^completionBlock)(void) in _scheduledRangeUpdateCompletionBlocks) { + completionBlock(); + } + [_scheduledRangeUpdateCompletionBlocks removeAllObjects]; + #if ASRangeControllerLoggingEnabled // NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; // BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; From 5b9302b6810a9ffaeb8060a4dd6bb928d2a81078 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 22 Mar 2016 14:38:50 -0700 Subject: [PATCH 26/27] Revert "Prevent deallocation of asyncDataSource and asyncDelegate in ASCollectionView and ASTableView" This reverts commit 3668f45286a56a87d200aea0299caee9d2e4a880. --- AsyncDisplayKit/ASCollectionView.mm | 9 +------- AsyncDisplayKit/ASTableView.mm | 9 +------- AsyncDisplayKit/Details/ASRangeController.mm | 23 +------------------ ...SRangeControllerUpdateRangeProtocol+Beta.h | 11 +-------- 4 files changed, 4 insertions(+), 48 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index a91c84fe..ae1f5792 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -1139,14 +1139,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass if (![node supportsRangeManagedInterfaceState]) { - // Grab a strong reference for data source and delegate to be sure they are not going away while executing - // the range update. This can happen in range updates while going back in the view controller hierarchy - __block id asyncDataSource = _asyncDataSource; - __block id asyncDelegate = _asyncDelegate; - [_rangeController scheduleRangeUpdateCompletion:^{ - asyncDataSource = nil; - asyncDelegate = nil; - }]; + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; } } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 9e5ad025..d8d9037d 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -1072,14 +1072,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass if (![node supportsRangeManagedInterfaceState]) { - // Grab a strong reference for data source and delegate to be sure they are not going away while executing - // the range update. This can happen in range updates while going back in the view controller hierarchy - __block id asyncDataSource = _asyncDataSource; - __block id asyncDelegate = _asyncDelegate; - [_rangeController scheduleRangeUpdateCompletion:^{ - asyncDataSource = nil; - asyncDelegate = nil; - }]; + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; } } diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index c9fffb4c..7935564a 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -27,7 +27,6 @@ BOOL _didUpdateCurrentRange; BOOL _didRegisterForNotifications; CFAbsoluteTime _pendingDisplayNodesTimestamp; - NSMutableArray *_scheduledRangeUpdateCompletionBlocks; } @end @@ -47,7 +46,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; _rangeIsValid = YES; _currentRangeMode = ASLayoutRangeModeInvalid; _didUpdateCurrentRange = NO; - _scheduledRangeUpdateCompletionBlocks = [NSMutableArray array]; [[[self class] allRangeControllersWeakSet] addObject:self]; @@ -113,15 +111,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; - (void)scheduleRangeUpdate { - [self scheduleRangeUpdateCompletion:nil]; -} - -- (void)scheduleRangeUpdateCompletion:(void (^)(void))completion -{ - if (completion) { - [_scheduledRangeUpdateCompletionBlocks addObject:completion]; - } - if (_queuedRangeUpdate) { return; } @@ -129,13 +118,8 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; // coalesce these events -- handling them multiple times per runloop is noisy and expensive _queuedRangeUpdate = YES; - __block id dataSource = _dataSource; - __block id delegate = _delegate; dispatch_async(dispatch_get_main_queue(), ^{ - [self _updateVisibleNodeIndexPaths]; - - dataSource = nil; - delegate = nil; + [self performRangeUpdate]; }); } @@ -336,11 +320,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; _rangeIsValid = YES; _queuedRangeUpdate = NO; - for (void (^completionBlock)(void) in _scheduledRangeUpdateCompletionBlocks) { - completionBlock(); - } - [_scheduledRangeUpdateCompletionBlocks removeAllObjects]; - #if ASRangeControllerLoggingEnabled // NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; // BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; diff --git a/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h b/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h index f9fcbf77..4f34cd1c 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h +++ b/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h @@ -49,12 +49,6 @@ */ - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; -/** - * Schedule a range update and call the completion block if finished. This drives updating the working - * ranges, and triggering their actions. - */ -- (void)scheduleRangeUpdateCompletion:(void (^)(void))completion; - @end @@ -70,10 +64,7 @@ @interface ASViewController (ASRangeControllerUpdateRangeProtocol) -/** - * Automatically adjust range mode based on view events if the containing node confirms to the - * ASRangeControllerUpdateRangeProtocol - */ +/// Automatically adjust range mode based on view events if the containing node confirms to the ASRangeControllerUpdateRangeProtocol @property (nonatomic, assign) BOOL automaticallyAdjustRangeModeBasedOnViewEvents; @end From e59431e702a0d2564afefd7572542630385f56ae Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 22 Mar 2016 18:39:23 -0700 Subject: [PATCH 27/27] [ASDisplayNode] Restore dispatch_async for clearContents, to prevent discarding contents for nodes that are simply moving to a new container. --- AsyncDisplayKit/ASDisplayNode.mm | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index fa696f58..e92f3252 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -2078,7 +2078,13 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [self setDisplaySuspended:NO]; } else { [self setDisplaySuspended:YES]; - [self clearContents]; + //schedule clear contents on next runloop + dispatch_async(dispatch_get_main_queue(), ^{ + ASDN::MutexLocker l(_propertyLock); + if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { + [self clearContents]; + } + }); } } else { // NOTE: This case isn't currently supported as setInterfaceState: isn't exposed externally, and all @@ -2090,7 +2096,13 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; } else { [[self asyncLayer] cancelAsyncDisplay]; - [self clearContents]; + //schedule clear contents on next runloop + dispatch_async(dispatch_get_main_queue(), ^{ + ASDN::MutexLocker l(_propertyLock); + if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { + [self clearContents]; + } + }); } } }